Merge commit '8bfe2df23f8ed4d77f9832264e28479f08c193b2' into dev-release
diff --git a/build.gradle b/build.gradle
index 730d291..c47befd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
 
 ext {
     androidSupportVersion = '25.4.0'
-    asmVersion = '8.0'  // When updating update tools/asmifier.py as well.
+    asmVersion = '8.0'  // When updating update tools/asmifier.py and Toolhelper as well.
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 87925c3..4fc504a 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -102,7 +102,7 @@
       swarming_tags: "vpython:native-python-wrapper"
       build_numbers: YES
       recipe {
-        properties: "mastername:internal.client.r8"
+        properties: "builder_group:internal.client.r8"
         name: "rex"
       }
       mixins: "build_limited_scripts_slave recipe"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 3651899..ab0d4dd 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -253,6 +254,12 @@
 
       InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
       if (options.isGeneratingClassFiles()) {
+        // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
+        SyntheticFinalization.Result result =
+            appView.getSyntheticItems().computeFinalSynthetics(appView);
+        if (result != null) {
+          appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+        }
         new CfApplicationWriter(
                 appView,
                 marker,
@@ -283,15 +290,22 @@
                   appView, inputApp, options, executor, timing, appView.appInfo().app());
           appView.setAppInfo(
               new AppInfo(
-                  app,
-                  appView.appInfo().getMainDexClasses(),
-                  appView.appInfo().getSyntheticItems().commit(app)));
+                  appView.appInfo().getSyntheticItems().commit(app),
+                  appView.appInfo().getMainDexClasses()));
           namingLens = NamingLens.getIdentityLens();
         }
+
+        // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
+        SyntheticFinalization.Result result =
+            appView.getSyntheticItems().computeFinalSynthetics(appView);
+        if (result != null) {
+          appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+        }
+
         new ApplicationWriter(
                 appView,
                 marker == null ? null : ImmutableList.copyOf(markers),
-                GraphLens.getIdentityLens(),
+                appView.graphLens(),
                 InitClassLens.getDefault(),
                 namingLens,
                 null)
@@ -338,9 +352,8 @@
     DexApplication cfApp = app.builder().replaceProgramClasses(nonDexProgramClasses).build();
     appView.setAppInfo(
         new AppInfo(
-            cfApp,
-            appView.appInfo().getMainDexClasses(),
-            appView.appInfo().getSyntheticItems().commit(cfApp)));
+            appView.appInfo().getSyntheticItems().commit(cfApp),
+            appView.appInfo().getMainDexClasses()));
     ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
     NamingLens prefixRewritingNamingLens =
         PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index ef71e03..332b864 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.L8TreePruner;
+import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -99,11 +99,7 @@
           });
       assert !options.cfToCfDesugar;
       if (shrink) {
-        AndroidApp r8CommandInputApp = r8Command.getInputApp();
-        InternalOptions r8CommandInternalOptions = r8Command.getInternalOptions();
-        // TODO(b/167843161): Disable temporarily enum unboxing in L8 due to naming issues.
-        r8CommandInternalOptions.enableEnumUnboxing = false;
-        R8.runForTesting(r8CommandInputApp, r8CommandInternalOptions);
+        R8.run(r8Command, executorService);
       } else if (d8Command != null) {
         D8.run(d8Command, executorService);
       }
@@ -131,15 +127,17 @@
 
       new IRConverter(appView, timing).convert(appView, executor);
 
+      SyntheticFinalization.Result result =
+          appView.getSyntheticItems().computeFinalSynthetics(appView);
+      if (result != null) {
+        appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+      }
+
       NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
       new GenericSignatureRewriter(appView, namingLens).run(appView.appInfo().classes(), executor);
 
       new CfApplicationWriter(
-              appView,
-              options.getMarker(Tool.L8),
-              GraphLens.getIdentityLens(),
-              namingLens,
-              null)
+              appView, options.getMarker(Tool.L8), appView.graphLens(), namingLens, null)
           .write(options.getClassFileConsumer());
       options.printWarnings();
     } catch (ExecutionException e) {
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 7ffc5ae..a1d8b26 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
@@ -352,7 +353,9 @@
         new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
     appInfo =
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
-            application, MainDexClasses.createEmptyMainDexClasses());
+            application,
+            ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+            MainDexClasses.createEmptyMainDexClasses());
   }
 
   private void analyze() {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7b94e97..9963a47 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -57,7 +57,6 @@
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLens;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
@@ -103,6 +102,7 @@
 import com.android.tools.r8.shaking.VerticalClassMerger;
 import com.android.tools.r8.shaking.VerticalClassMergerGraphLens;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -314,13 +314,7 @@
       InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
       BackportedMethodRewriter.registerAssumedLibraryTypes(options);
       if (options.enableEnumUnboxing) {
-        if (appView.definitionFor(options.itemFactory.enumUnboxingUtilityType) != null) {
-          // The enum unboxing utility class can be created only during cf to dex compilation.
-          // If this is true, we are recompiling the dex application with R8 (compilation-steps).
-          options.enableEnumUnboxing = false;
-        } else {
-          EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
-        }
+        EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
       }
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
@@ -412,12 +406,6 @@
           TreePruner pruner = new TreePruner(appViewWithLiveness);
           DirectMappedDexApplication prunedApp = pruner.run();
 
-          if (options.enableEnumUnboxing) {
-            DexProgramClass utilityClass =
-                EnumUnboxingRewriter.synthesizeEmptyEnumUnboxingUtilityClass(appView);
-            prunedApp = prunedApp.builder().addProgramClass(utilityClass).build();
-          }
-
           // Recompute the subtyping information.
           Set<DexType> removedClasses = pruner.getRemovedClasses();
           appView.setAppInfo(
@@ -614,8 +602,9 @@
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
         IRConverter converter = new IRConverter(appView, timing, printer, mainDexTracingResult);
-        DexApplication application = converter.optimize(executorService).asDirect();
-        appView.setAppInfo(appView.appInfo().rebuild(previous -> application));
+        DexApplication application =
+            converter.optimize(appViewWithLiveness, executorService).asDirect();
+        appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(previous -> application));
       } finally {
         timing.end();
       }
@@ -818,6 +807,24 @@
         }
       }
 
+      // Add automatic main dex classes to an eventual manual list of classes.
+      if (!options.mainDexKeepRules.isEmpty()) {
+        appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
+      }
+
+      SyntheticFinalization.Result result =
+          appView.getSyntheticItems().computeFinalSynthetics(appView);
+      if (result != null) {
+        if (appView.appInfo().hasLiveness()) {
+          appViewWithLiveness.setAppInfo(
+              appViewWithLiveness
+                  .appInfo()
+                  .rebuildWithLiveness(result.commit, result.removedSyntheticClasses));
+        } else {
+          appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
+        }
+      }
+
       // Perform minification.
       NamingLens namingLens;
       if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -871,11 +878,6 @@
         assert !options.isShrinking();
       }
 
-      // Add automatic main dex classes to an eventual manual list of classes.
-      if (!options.mainDexKeepRules.isEmpty()) {
-        appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
-      }
-
       // Validity checks.
       assert getDirectApp(appView).verifyCodeObjectsOwners();
       assert appView.appInfo().classes().stream().allMatch(clazz -> clazz.isValid(options));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 6efe395..7c78db1 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -551,7 +551,7 @@
               : getDesugaringState();
 
       FeatureSplitConfiguration featureSplitConfiguration =
-          !featureSplits.isEmpty() ? new FeatureSplitConfiguration(featureSplits, reporter) : null;
+          !featureSplits.isEmpty() ? new FeatureSplitConfiguration(featureSplits) : null;
 
       R8Command command =
           new R8Command(
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 8174420..b01f9a3 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -168,7 +168,7 @@
         processMethod(method);
       }
 
-      if (classDef.hasAnnotations()) {
+      if (classDef.hasClassOrMemberAnnotations()) {
         processAnnotations(classDef);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 0dcca76..6a21762 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.AndroidApp;
@@ -191,7 +192,7 @@
         new ApplicationWriter(
             AppView.createForD8(AppInfo.createInitialAppInfo(app)),
             null,
-            null,
+            GraphLens.getIdentityLens(),
             InitClassLens.getDefault(),
             NamingLens.getIdentityLens(),
             null);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 393297a..d9103a5 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -54,6 +54,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.InternalOptions;
@@ -319,6 +320,7 @@
     for (DexType type : mapping.getTypes()) {
       if (type.isClassType()) {
         assert DexString.isValidSimpleName(apiLevel, type.getName());
+        assert SyntheticItems.verifyNotInternalSynthetic(type);
       }
     }
 
@@ -1270,7 +1272,7 @@
 
 
     public int getOffsetForAnnotationsDirectory(DexProgramClass clazz) {
-      if (!clazz.hasAnnotations()) {
+      if (!clazz.hasClassOrMemberAnnotations()) {
         return Constants.NO_OFFSET;
       }
       int offset = annotationDirectories.getInt(clazzToAnnotationDirectory.get(clazz));
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 5e31d99..2e0ab82 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
@@ -28,6 +29,7 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -74,6 +76,7 @@
   private static final int MAX_PREFILL_ENTRIES = MAX_ENTRIES - 5000;
 
   private final int id;
+  private final GraphLens graphLens;
   private final VirtualFileIndexedItemCollection indexedItems;
   private final IndexedItemTransaction transaction;
   private final FeatureSplit featureSplit;
@@ -118,6 +121,7 @@
       DexProgramClass primaryClass,
       FeatureSplit featureSplit) {
     this.id = id;
+    this.graphLens = graphLens;
     this.indexedItems = new VirtualFileIndexedItemCollection(graphLens, initClassLens, namingLens);
     this.transaction =
         new IndexedItemTransaction(indexedItems, appView, graphLens, initClassLens, namingLens);
@@ -438,14 +442,20 @@
     }
 
     protected Map<FeatureSplit, Set<DexProgramClass>> removeFeatureSplitClassesGetMapping() {
-      if (options.featureSplitConfiguration == null) {
+      assert appView.appInfo().hasClassHierarchy() == appView.enableWholeProgramOptimizations();
+      if (!appView.appInfo().hasClassHierarchy()) {
+        return ImmutableMap.of();
+      }
+
+      ClassToFeatureSplitMap classToFeatureSplitMap =
+          appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap();
+      if (classToFeatureSplitMap.isEmpty()) {
         return ImmutableMap.of();
       }
 
       // Pull out the classes that should go into feature splits.
       Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
-          options.featureSplitConfiguration.getFeatureSplitClasses(
-              classes, appView.appInfo().app().getProguardMap());
+          classToFeatureSplitMap.getFeatureSplitClasses(classes);
       if (featureSplitClasses.size() > 0) {
         for (Set<DexProgramClass> featureClasses : featureSplitClasses.values()) {
           classes.removeAll(featureClasses);
@@ -627,12 +637,12 @@
 
     @Override
     public boolean addField(DexField field) {
-      return fields.add(field);
+      return fields.add(graphLens.lookupField(field));
     }
 
     @Override
     public boolean addMethod(DexMethod method) {
-      return methods.add(method);
+      return methods.add(graphLens.lookupMethod(method));
     }
 
     @Override
@@ -647,7 +657,9 @@
 
     @Override
     public boolean addType(DexType type) {
-      return types.add(type);
+      DexType rewritten = graphLens.lookupType(type);
+      assert SyntheticItems.verifyNotInternalSynthetic(rewritten);
+      return types.add(rewritten);
     }
 
     @Override
@@ -684,18 +696,19 @@
 
     @Override
     public DexString getRenamedDescriptor(DexType type) {
-      return namingLens.lookupDescriptor(type);
+      return namingLens.lookupDescriptor(graphLens.lookupType(type));
     }
 
     @Override
     public DexString getRenamedName(DexMethod method) {
-      assert namingLens.verifyRenamingConsistentWithResolution(method);
-      return namingLens.lookupName(method);
+      DexMethod mappedMethod = graphLens.lookupMethod(method);
+      assert namingLens.verifyRenamingConsistentWithResolution(mappedMethod);
+      return namingLens.lookupName(mappedMethod);
     }
 
     @Override
     public DexString getRenamedName(DexField field) {
-      return namingLens.lookupName(field);
+      return namingLens.lookupName(graphLens.lookupField(field));
     }
   }
 
@@ -748,12 +761,12 @@
 
     @Override
     public boolean addField(DexField field) {
-      return maybeInsert(field, fields, base.fields);
+      return maybeInsert(base.graphLens.lookupField(field), fields, base.fields);
     }
 
     @Override
     public boolean addMethod(DexMethod method) {
-      return maybeInsert(method, methods, base.methods);
+      return maybeInsert(base.graphLens.lookupMethod(method), methods, base.methods);
     }
 
     @Override
@@ -768,7 +781,9 @@
 
     @Override
     public boolean addType(DexType type) {
-      return maybeInsert(type, types, base.types);
+      DexType rewritten = base.graphLens.lookupType(type);
+      assert SyntheticItems.verifyNotInternalSynthetic(rewritten);
+      return maybeInsert(rewritten, types, base.types);
     }
 
     @Override
@@ -793,18 +808,19 @@
 
     @Override
     public DexString getRenamedDescriptor(DexType type) {
-      return namingLens.lookupDescriptor(type);
+      return namingLens.lookupDescriptor(base.graphLens.lookupType(type));
     }
 
     @Override
     public DexString getRenamedName(DexMethod method) {
-      assert namingLens.verifyRenamingConsistentWithResolution(method);
-      return namingLens.lookupName(method);
+      DexMethod mappedMethod = base.graphLens.lookupMethod(method);
+      assert namingLens.verifyRenamingConsistentWithResolution(mappedMethod);
+      return namingLens.lookupName(mappedMethod);
     }
 
     @Override
     public DexString getRenamedName(DexField field) {
-      return namingLens.lookupName(field);
+      return namingLens.lookupName(base.graphLens.lookupField(field));
     }
 
     int getNumberOfMethods() {
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
new file mode 100644
index 0000000..6ba885d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -0,0 +1,140 @@
+// 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.features;
+
+import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class ClassToFeatureSplitMap {
+
+  private final Map<DexType, FeatureSplit> classToFeatureSplitMap = new IdentityHashMap<>();
+
+  private ClassToFeatureSplitMap() {}
+
+  public static ClassToFeatureSplitMap createEmptyClassToFeatureSplitMap() {
+    return new ClassToFeatureSplitMap();
+  }
+
+  public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return createInitialClassToFeatureSplitMap(appView.options());
+  }
+
+  public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap(
+      InternalOptions options) {
+    DexItemFactory dexItemFactory = options.dexItemFactory();
+    FeatureSplitConfiguration featureSplitConfiguration = options.featureSplitConfiguration;
+
+    ClassToFeatureSplitMap result = new ClassToFeatureSplitMap();
+    if (featureSplitConfiguration == null) {
+      return result;
+    }
+
+    for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+      for (ProgramResourceProvider programResourceProvider :
+          featureSplit.getProgramResourceProviders()) {
+        try {
+          for (ProgramResource programResource : programResourceProvider.getProgramResources()) {
+            for (String classDescriptor : programResource.getClassDescriptors()) {
+              DexType type = dexItemFactory.createType(classDescriptor);
+              result.classToFeatureSplitMap.put(type, featureSplit);
+            }
+          }
+        } catch (ResourceException e) {
+          throw options.reporter.fatalError(e.getMessage());
+        }
+      }
+    }
+    return result;
+  }
+
+  public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
+      Set<DexProgramClass> classes) {
+    Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
+    for (DexProgramClass clazz : classes) {
+      FeatureSplit featureSplit = getFeatureSplit(clazz);
+      if (featureSplit != null && !featureSplit.isBase()) {
+        result.computeIfAbsent(featureSplit, ignore -> Sets.newIdentityHashSet()).add(clazz);
+      }
+    }
+    return result;
+  }
+
+  public FeatureSplit getFeatureSplit(DexProgramClass clazz) {
+    return getFeatureSplit(clazz.getType());
+  }
+
+  public FeatureSplit getFeatureSplit(DexType type) {
+    return classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
+  }
+
+  public boolean isEmpty() {
+    return classToFeatureSplitMap.isEmpty();
+  }
+
+  public boolean isInBase(DexProgramClass clazz) {
+    return getFeatureSplit(clazz).isBase();
+  }
+
+  public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
+    FeatureSplit split = getFeatureSplit(clazz);
+    return split.isBase() || split == getFeatureSplit(context);
+  }
+
+  public boolean isInFeature(DexProgramClass clazz) {
+    return !isInBase(clazz);
+  }
+
+  public boolean isInSameFeatureOrBothInBase(ProgramMethod a, ProgramMethod b) {
+    return isInSameFeatureOrBothInBase(a.getHolder(), b.getHolder());
+  }
+
+  public boolean isInSameFeatureOrBothInBase(DexProgramClass a, DexProgramClass b) {
+    return getFeatureSplit(a) == getFeatureSplit(b);
+  }
+
+  public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
+    ClassToFeatureSplitMap rewrittenClassToFeatureSplitMap = new ClassToFeatureSplitMap();
+    classToFeatureSplitMap.forEach(
+        (type, featureSplit) -> {
+          DexType rewrittenType = lens.lookupType(type);
+          if (rewrittenType.isIntType()) {
+            // The type was removed by enum unboxing.
+            return;
+          }
+          FeatureSplit existing =
+              rewrittenClassToFeatureSplitMap.classToFeatureSplitMap.put(
+                  rewrittenType, featureSplit);
+          // If we map two classes to the same class then they must be from the same feature split.
+          assert existing == null || existing == featureSplit;
+        });
+    return rewrittenClassToFeatureSplitMap;
+  }
+
+  public ClassToFeatureSplitMap withoutPrunedClasses(Set<DexType> prunedClasses) {
+    ClassToFeatureSplitMap classToFeatureSplitMapAfterPruning = new ClassToFeatureSplitMap();
+    classToFeatureSplitMap.forEach(
+        (type, featureSplit) -> {
+          if (!prunedClasses.contains(type)) {
+            classToFeatureSplitMapAfterPruning.classToFeatureSplitMap.put(type, featureSplit);
+          }
+        });
+    return classToFeatureSplitMapAfterPruning;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index 6e5b2ab..c357177 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -6,63 +6,19 @@
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.FeatureSplit;
-import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Reporter;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 public class FeatureSplitConfiguration {
 
   private final List<FeatureSplit> featureSplits;
 
-  // TODO(141451259): Consider doing the mapping from DexType to Feature (with support in mapper).
-  private final Map<String, FeatureSplit> javaTypeToFeatureSplitMapping = new HashMap<>();
-
-  public FeatureSplitConfiguration(List<FeatureSplit> featureSplits, Reporter reporter) {
+  public FeatureSplitConfiguration(List<FeatureSplit> featureSplits) {
     this.featureSplits = featureSplits;
-    for (FeatureSplit featureSplit : featureSplits) {
-      for (ProgramResourceProvider programResourceProvider :
-          featureSplit.getProgramResourceProviders()) {
-        try {
-          for (ProgramResource programResource : programResourceProvider.getProgramResources()) {
-            for (String classDescriptor : programResource.getClassDescriptors()) {
-              javaTypeToFeatureSplitMapping.put(
-                  DescriptorUtils.descriptorToJavaType(classDescriptor), featureSplit);
-            }
-          }
-        } catch (ResourceException e) {
-          throw reporter.fatalError(e.getMessage());
-        }
-      }
-    }
-  }
-
-  public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
-      Set<DexProgramClass> classes, ClassNameMapper mapper) {
-    Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
-
-    for (DexProgramClass programClass : classes) {
-      String originalClassName =
-          DescriptorUtils.descriptorToJavaType(programClass.type.toDescriptorString(), mapper);
-      FeatureSplit featureSplit = javaTypeToFeatureSplitMapping.get(originalClassName);
-      if (featureSplit != null) {
-        result.computeIfAbsent(featureSplit, f -> Sets.newIdentityHashSet()).add(programClass);
-      }
-    }
-    return result;
   }
 
   public static class DataResourceProvidersAndConsumer {
@@ -108,37 +64,7 @@
     return result;
   }
 
-  public boolean inBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
-    FeatureSplit split = getFeatureSplit(clazz);
-    return split.isBase() || split == getFeatureSplit(context);
-  }
-
-  public boolean isInFeature(DexProgramClass clazz) {
-    return !isInBase(clazz);
-  }
-
-  public boolean isInBase(DexProgramClass clazz) {
-    return getFeatureSplit(clazz).isBase();
-  }
-
-  public boolean inSameFeatureOrBothInBase(ProgramMethod a, ProgramMethod b) {
-    return inSameFeatureOrBothInBase(a.getHolder(), b.getHolder());
-  }
-
-  public boolean inSameFeatureOrBothInBase(DexProgramClass a, DexProgramClass b) {
-    return getFeatureSplit(a) == getFeatureSplit(b);
-  }
-
   public List<FeatureSplit> getFeatureSplits() {
     return featureSplits;
   }
-
-  public FeatureSplit getFeatureSplitFromClassDescriptor(String classDescriptor) {
-    return javaTypeToFeatureSplitMapping.get(DescriptorUtils.descriptorToJavaType(classDescriptor));
-  }
-
-  public FeatureSplit getFeatureSplit(DexProgramClass clazz) {
-    return javaTypeToFeatureSplitMapping.getOrDefault(
-        clazz.type.toSourceString(), FeatureSplit.BASE);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 788b358..4bb68b9 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.features.FeatureSplitConfiguration;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.utils.OptionalBool;
 
 /**
@@ -15,21 +15,18 @@
 public class AccessControl {
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz, ProgramMethod context, AppView<?> appView) {
+      DexClass clazz, ProgramMethod context, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return isClassAccessible(
-        clazz, context.getHolder(), appView.options().featureSplitConfiguration);
+        clazz, context.getHolder(), appView.appInfo().getClassToFeatureSplitMap());
   }
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz,
-      DexProgramClass context,
-      FeatureSplitConfiguration featureSplitConfiguration) {
+      DexClass clazz, DexProgramClass context, ClassToFeatureSplitMap classToFeatureSplitMap) {
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getType())) {
       return OptionalBool.FALSE;
     }
-    if (featureSplitConfiguration != null
-        && clazz.isProgramClass()
-        && !featureSplitConfiguration.inBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
+    if (clazz.isProgramClass()
+        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
@@ -73,7 +70,7 @@
       DexProgramClass context,
       AppInfoWithClassHierarchy appInfo) {
     OptionalBool classAccessibility =
-        isClassAccessible(holder, context, appInfo.options().featureSplitConfiguration);
+        isClassAccessible(holder, context, appInfo.getClassToFeatureSplitMap());
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index c49eb28..6da4e08 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy.CreateDesugaringViewOnAppInfo;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.CommittedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collection;
@@ -31,30 +32,28 @@
   public static AppInfo createInitialAppInfo(
       DexApplication application, MainDexClasses mainDexClasses) {
     return new AppInfo(
-        application,
-        mainDexClasses,
-        SyntheticItems.createInitialSyntheticItems(),
-        new BooleanBox());
+        SyntheticItems.createInitialSyntheticItems().commit(application), mainDexClasses);
   }
 
-  public AppInfo(
-      DexApplication application,
-      MainDexClasses mainDexClasses,
-      SyntheticItems.CommittedItems committedItems) {
-    this(application, mainDexClasses, committedItems.toSyntheticItems(), new BooleanBox());
+  public AppInfo(CommittedItems committedItems, MainDexClasses mainDexClasses) {
+    this(
+        committedItems.getApplication(),
+        committedItems.toSyntheticItems(),
+        mainDexClasses,
+        new BooleanBox());
   }
 
   // For desugaring.
   // This is a view onto the app info and is the only place the pending synthetics are shared.
-  AppInfo(CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
-    this(appInfo.app, appInfo.mainDexClasses, appInfo.syntheticItems, appInfo.obsolete);
+  AppInfo(AppInfoWithClassHierarchy.CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
+    this(appInfo.app, appInfo.syntheticItems, appInfo.mainDexClasses, appInfo.obsolete);
     assert witness != null;
   }
 
   private AppInfo(
       DexApplication application,
-      MainDexClasses mainDexClasses,
       SyntheticItems syntheticItems,
+      MainDexClasses mainDexClasses,
       BooleanBox obsolete) {
     this.app = application;
     this.dexItemFactory = application.dexItemFactory;
@@ -105,7 +104,7 @@
 
   public void addSynthesizedClass(DexProgramClass clazz, boolean addToMainDexClasses) {
     assert checkIfObsolete();
-    syntheticItems.addSyntheticClass(clazz);
+    syntheticItems.addLegacySyntheticClass(clazz);
     if (addToMainDexClasses && !mainDexClasses.isEmpty()) {
       mainDexClasses.add(clazz);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 68ce78b..a1e3d15 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.TraversalContinuation.BREAK;
 import static com.android.tools.r8.utils.TraversalContinuation.CONTINUE;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.ArrayCloneMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.ClassNotFoundResult;
@@ -16,6 +17,8 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.CommittedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
@@ -47,24 +50,30 @@
   }
 
   public static AppInfoWithClassHierarchy createInitialAppInfoWithClassHierarchy(
-      DexApplication application, MainDexClasses mainDexClasses) {
+      DexApplication application,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      MainDexClasses mainDexClasses) {
     return new AppInfoWithClassHierarchy(
-        application,
-        mainDexClasses,
-        SyntheticItems.createInitialSyntheticItems().commit(application));
+        SyntheticItems.createInitialSyntheticItems().commit(application),
+        classToFeatureSplitMap,
+        mainDexClasses);
   }
 
-  // For AppInfoWithLiveness.
+  private final ClassToFeatureSplitMap classToFeatureSplitMap;
+
+  // For AppInfoWithLiveness subclass.
   protected AppInfoWithClassHierarchy(
-      DexApplication application,
-      MainDexClasses mainDexClasses,
-      SyntheticItems.CommittedItems committedItems) {
-    super(application, mainDexClasses, committedItems);
+      CommittedItems committedItems,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      MainDexClasses mainDexClasses) {
+    super(committedItems, mainDexClasses);
+    this.classToFeatureSplitMap = classToFeatureSplitMap;
   }
 
   // For desugaring.
   private AppInfoWithClassHierarchy(CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
     super(witness, appInfo);
+    this.classToFeatureSplitMap = ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap();
   }
 
   public static AppInfoWithClassHierarchy createForDesugaring(AppInfo appInfo) {
@@ -72,10 +81,20 @@
     return new AppInfoWithClassHierarchy(WITNESS, appInfo);
   }
 
-  public AppInfoWithClassHierarchy rebuild(Function<DexApplication, DexApplication> fn) {
-    DexApplication application = fn.apply(app());
+  public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(CommittedItems commit) {
+    return new AppInfoWithClassHierarchy(commit, getClassToFeatureSplitMap(), getMainDexClasses());
+  }
+
+  public AppInfoWithClassHierarchy rebuildWithClassHierarchy(
+      Function<DexApplication, DexApplication> fn) {
     return new AppInfoWithClassHierarchy(
-        application, getMainDexClasses(), getSyntheticItems().commit(application));
+        getSyntheticItems().commit(fn.apply(app())),
+        getClassToFeatureSplitMap(),
+        getMainDexClasses());
+  }
+
+  public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
+    return classToFeatureSplitMap;
   }
 
   @Override
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 f853b30..de7599a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -12,7 +12,9 @@
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 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.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
@@ -71,8 +73,10 @@
     return builder.build();
   }
 
-  public boolean hasServiceImplementationsInFeature(DexType serviceType) {
-    if (appView.options().featureSplitConfiguration == null) {
+  public boolean hasServiceImplementationsInFeature(
+      AppView<AppInfoWithLiveness> appView, DexType serviceType) {
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    if (classToFeatureSplitMap.isEmpty()) {
       return false;
     }
     Map<FeatureSplit, List<DexType>> featureImplementations = services.get(serviceType);
@@ -89,12 +93,12 @@
     }
     // Check if service is defined feature
     DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
-    if (appView.options().featureSplitConfiguration.isInFeature(serviceClass)) {
+    if (classToFeatureSplitMap.isInFeature(serviceClass)) {
       return true;
     }
     for (DexType dexType : featureImplementations.get(FeatureSplit.BASE)) {
       DexProgramClass implementationClass = appView.definitionForProgramType(dexType);
-      if (appView.options().featureSplitConfiguration.isInFeature(implementationClass)) {
+      if (classToFeatureSplitMap.isInFeature(implementationClass)) {
         return true;
       }
     }
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 2a181bc..f89f265 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -135,9 +137,11 @@
 
   public static AppView<AppInfoWithClassHierarchy> createForR8(
       DexApplication application, MainDexClasses mainDexClasses) {
+    ClassToFeatureSplitMap classToFeatureSplitMap =
+        ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(application.options);
     AppInfoWithClassHierarchy appInfo =
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
-            application, mainDexClasses);
+            application, classToFeatureSplitMap, mainDexClasses);
     return new AppView<>(
         appInfo, WholeProgramOptimizations.ON, defaultPrefixRewritingMapper(appInfo));
   }
@@ -266,6 +270,10 @@
     return wholeProgramOptimizations == WholeProgramOptimizations.ON;
   }
 
+  public SyntheticItems getSyntheticItems() {
+    return appInfo.getSyntheticItems();
+  }
+
   public CallSiteOptimizationInfoPropagator callSiteOptimizationInfoPropagator() {
     return callSiteOptimizationInfoPropagator;
   }
@@ -358,6 +366,10 @@
     return initClassLens;
   }
 
+  public boolean hasInitClassLens() {
+    return initClassLens != null;
+  }
+
   public void setInitClassLens(InitClassLens initClassLens) {
     this.initClassLens = initClassLens;
   }
@@ -514,6 +526,11 @@
     }
   }
 
+  public void rewriteWithApplication(DirectMappedDexApplication application) {
+    assert application != null;
+    rewriteWithLens(null, application, withLiveness());
+  }
+
   public void rewriteWithLensAndApplication(
       NestedGraphLens lens, DirectMappedDexApplication application) {
     assert lens != null;
@@ -525,9 +542,14 @@
       NestedGraphLens lens,
       DirectMappedDexApplication application,
       AppView<AppInfoWithLiveness> appView) {
-    boolean changed = appView.setGraphLens(lens);
-    assert changed;
-    assert application.verifyWithLens(lens);
+    if (lens != null) {
+      boolean changed = appView.setGraphLens(lens);
+      assert changed;
+      assert application.verifyWithLens(lens);
+    }
     appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
+    if (appView.hasInitClassLens()) {
+      appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java b/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
index 56a1733..ec2f4c6 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
@@ -20,4 +20,9 @@
   public DexField getInitClassField(DexType type) {
     throw new Unreachable("Unexpected InitClass instruction for `" + type.toSourceString() + "`");
   }
+
+  @Override
+  public InitClassLens rewrittenWithLens(GraphLens lens) {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
index e1965be..237838f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
@@ -17,6 +17,10 @@
     this.value = value;
   }
 
+  public DexValue getValue() {
+    return value;
+  }
+
   @Override
   public int hashCode() {
     return name.hashCode() + value.hashCode() * 3;
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 67870ab..516edc0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.synthesis.SyntheticDefinitionsProvider;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 528b79b..e3bf2d8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -723,6 +723,10 @@
     return enclosingMethod;
   }
 
+  public void setEnclosingMethodAttribute(EnclosingMethodAttribute enclosingMethod) {
+    this.enclosingMethod = enclosingMethod;
+  }
+
   public void clearEnclosingMethodAttribute() {
     enclosingMethod = null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index 9391a83..4125ca0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -48,6 +48,10 @@
     return DexProgramClass.asProgramClassOrNull(definitionFor(type, context));
   }
 
+  default DexProgramClass programDefinitionFor(DexType type, ProgramMethod context) {
+    return programDefinitionFor(type, context.getHolder());
+  }
+
   default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
       DexClass definitionForHolder(DexEncodedMember<D, R> member, ProgramMethod context) {
     return definitionForHolder(member.toReference(), context.getHolder());
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index 0ed6769..e480799 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -30,6 +30,14 @@
     }
   }
 
+  public DexAnnotationElement getElement(int i) {
+    return elements[i];
+  }
+
+  public int getNumberOfElements() {
+    return elements.length;
+  }
+
   @Override
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     // Should never be called.
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 2ca1b48..c40383d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -73,11 +73,13 @@
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
+import com.google.common.hash.Hasher;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -268,6 +270,41 @@
     assert parameterAnnotationsList != null;
   }
 
+  public void hashSyntheticContent(Hasher hasher) {
+    // Method holder does not contribute to the synthetic hash (it is freely chosen).
+    // Method name does not contribute to the synthetic hash (it is freely chosen).
+    method.proto.hashSyntheticContent(hasher);
+    hasher.putInt(accessFlags.getAsCfAccessFlags());
+    assert annotations().isEmpty();
+    assert parameterAnnotationsList.isEmpty();
+    assert code != null;
+    // TODO(b/158159959): Implement a more precise hashing on code objects.
+    if (code.isCfCode()) {
+      CfCode cfCode = code.asCfCode();
+      hasher.putInt(cfCode.instructions.size());
+      for (CfInstruction instruction : cfCode.instructions) {
+        hasher.putInt(instruction.getClass().hashCode());
+      }
+    } else {
+      assert code.isDexCode();
+      hasher.putInt(code.hashCode());
+    }
+  }
+
+  public boolean isSyntheticContentEqual(DexEncodedMethod other) {
+    return syntheticCompareTo(other) == 0;
+  }
+
+  public int syntheticCompareTo(DexEncodedMethod other) {
+    assert annotations().isEmpty();
+    assert parameterAnnotationsList.isEmpty();
+    return Comparator.comparing(DexEncodedMethod::proto, DexProto::slowCompareTo)
+        .thenComparingInt(m -> m.accessFlags.getAsCfAccessFlags())
+        // TODO(b/158159959): Implement structural compareTo on code.
+        .thenComparing(m -> m.getCode().toString())
+        .compare(this, other);
+  }
+
   public DexType getHolderType() {
     return getReference().holder;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 19b793d..a58ec39 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -13,12 +13,10 @@
 public class DexField extends DexMember<DexEncodedField, DexField> {
 
   public final DexType type;
-  public final DexString name;
 
   DexField(DexType holder, DexType type, DexString name, boolean skipNameValidationForTesting) {
-    super(holder);
+    super(holder, name);
     this.type = type;
-    this.name = name;
     if (!skipNameValidationForTesting && !name.isValidFieldName()) {
       throw new CompilationError(
           "Field name '" + name.toString() + "' cannot be represented in dex format.");
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 91a70a9..4c3779e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
@@ -443,12 +442,6 @@
       createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
   public final DexType nestConstructorType = createStaticallyKnownType(nestConstructorDescriptor);
 
-  public final DexString enumUnboxingUtilityDescriptor =
-      createString(
-          "Lcom/android/tools/r8/" + EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
-  public final DexType enumUnboxingUtilityType =
-      createStaticallyKnownType(enumUnboxingUtilityDescriptor);
-
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
   public final StringBuildingMethods stringBufferMethods =
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 02fd431..2a429d6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -7,10 +7,13 @@
     extends DexReference implements PresortedComparable<R> {
 
   public final DexType holder;
+  public final DexString name;
 
-  public DexMember(DexType holder) {
+  public DexMember(DexType holder, DexString name) {
     assert holder != null;
     this.holder = holder;
+    assert name != null;
+    this.name = name;
   }
 
   public DexEncodedMember<?, ?> lookupOnClass(DexClass clazz) {
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 493bea3..26f114f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -18,12 +18,10 @@
 public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
 
   public final DexProto proto;
-  public final DexString name;
 
   DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) {
-    super(holder);
+    super(holder, name);
     this.proto = proto;
-    this.name = name;
     if (!skipNameValidationForTesting && !name.isValidMethodName()) {
       throw new CompilationError(
           "Method name '" + name + "' in class '" + holder.toSourceString() +
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index a285398..f0efe20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -269,7 +269,7 @@
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     assert getEnclosingMethodAttribute() == null;
     assert getInnerClasses().isEmpty();
-    if (hasAnnotations()) {
+    if (hasClassOrMemberAnnotations()) {
       mixedItems.setAnnotationsDirectoryForClass(this, new DexAnnotationDirectory(this));
     }
   }
@@ -363,7 +363,8 @@
     return hasMethods() || hasFields();
   }
 
-  public boolean hasAnnotations() {
+  /** Determine if the class or any of its methods/fields has any attributes. */
+  public boolean hasClassOrMemberAnnotations() {
     return !annotations().isEmpty()
         || hasAnnotations(methodCollection)
         || hasAnnotations(staticFields)
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 77b9c42..09985c3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.naming.NamingLens;
+import com.google.common.hash.Hasher;
 
 public class DexProto extends IndexedDexItem implements PresortedComparable<DexProto> {
 
@@ -97,4 +98,11 @@
     builder.append(lens.lookupDescriptor(returnType));
     return builder.toString();
   }
+
+  public void hashSyntheticContent(Hasher hasher) {
+    hasher.putInt(returnType.hashCode());
+    for (DexType param : parameters.values) {
+      hasher.putInt(param.hashCode());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index fc14405..bc7e26f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -11,7 +11,7 @@
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
-import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME;
+import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.google.common.base.Predicates;
@@ -291,6 +292,9 @@
     return
     // Hygienic suffix.
     name.contains(COMPANION_CLASS_NAME_SUFFIX)
+        // New and hygienic synthesis infrastructure.
+        || name.contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+        || name.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
         // Only generated in core lib.
         || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
         || name.contains(TYPE_WRAPPER_SUFFIX)
@@ -308,7 +312,7 @@
   private static boolean isSynthesizedTypeThatCouldBeDuplicated(String name) {
     // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
     // newer releases can be used to merge previous builds.
-    return name.contains(ENUM_UNBOXING_UTILITY_CLASS_NAME) // Global singleton.
+    return name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX) // Shared among enums.
         || name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(DISPATCH_CLASS_NAME_SUFFIX) // Shared on reference.
diff --git a/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java b/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java
index 4f0a854..07e4dc2 100644
--- a/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java
+++ b/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java
@@ -28,4 +28,16 @@
   public boolean isFinal() {
     return true;
   }
+
+  @Override
+  public InitClassLens rewrittenWithLens(GraphLens lens) {
+    InitClassLens.Builder builder = InitClassLens.builder();
+    mapping.forEach(
+        (type, field) -> {
+          DexType rewrittenType = lens.lookupType(type);
+          DexField rewrittenField = lens.lookupField(field);
+          builder.map(rewrittenType, rewrittenField);
+        });
+    return builder.build();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/InitClassLens.java b/src/main/java/com/android/tools/r8/graph/InitClassLens.java
index cac3870..e3e7fd2 100644
--- a/src/main/java/com/android/tools/r8/graph/InitClassLens.java
+++ b/src/main/java/com/android/tools/r8/graph/InitClassLens.java
@@ -23,6 +23,8 @@
     return false;
   }
 
+  public abstract InitClassLens rewrittenWithLens(GraphLens lens);
+
   public static class Builder {
 
     private final Map<DexType, DexField> mapping = new ConcurrentHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index ecc7505..9839a80 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -55,6 +55,7 @@
       Collection<DexCallSite> callSites,
       Collection<DexMethodHandle> methodHandles) {
     assert appInfo != null;
+    assert graphLens != null;
     assert classes != null;
     assert protos != null;
     assert types != null;
@@ -251,11 +252,11 @@
   }
 
   public int getOffsetFor(DexField field) {
-    return getOffsetFor(field, fields);
+    return getOffsetFor(graphLens.lookupField(field), fields);
   }
 
   public int getOffsetFor(DexMethod method) {
-    return getOffsetFor(method, methods);
+    return getOffsetFor(graphLens.lookupMethod(method), methods);
   }
 
   public int getOffsetFor(DexString string) {
@@ -263,7 +264,7 @@
   }
 
   public int getOffsetFor(DexType type) {
-    return getOffsetFor(type, types);
+    return getOffsetFor(graphLens.lookupType(type), types);
   }
 
   public int getOffsetFor(DexCallSite callSite) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
index c244576..c92c6d4 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Iterator;
 import java.util.Set;
@@ -18,9 +19,9 @@
     this.packageDescriptor = packageDescriptor;
   }
 
-  public void add(DexProgramClass clazz) {
+  public boolean add(DexProgramClass clazz) {
     assert clazz.getType().getPackageDescriptor().equals(packageDescriptor);
-    classes.add(clazz);
+    return classes.add(clazz);
   }
 
   public String getLastPackageName() {
@@ -43,6 +44,10 @@
     forEach(clazz -> clazz.forEachProgramMethod(consumer));
   }
 
+  public Set<DexProgramClass> classesInPackage() {
+    return ImmutableSet.copyOf(classes);
+  }
+
   @Override
   public Iterator<DexProgramClass> iterator() {
     return classes.iterator();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
index 57fc75a..fe9a64c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -16,15 +16,27 @@
     this.packages = packages;
   }
 
-  public static ProgramPackageCollection create(AppView<?> appView) {
-    Map<String, ProgramPackage> packages = new HashMap<>();
+  public static ProgramPackageCollection createWithAllProgramClasses(AppView<?> appView) {
     assert !appView.appInfo().getSyntheticItems().hasPendingSyntheticClasses();
+    ProgramPackageCollection programPackages = new ProgramPackageCollection(new HashMap<>());
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      packages
-          .computeIfAbsent(clazz.getType().getPackageDescriptor(), ProgramPackage::new)
-          .add(clazz);
+      programPackages.addProgramClass(clazz);
     }
-    return new ProgramPackageCollection(packages);
+    return programPackages;
+  }
+
+  public static ProgramPackageCollection createEmpty() {
+    return new ProgramPackageCollection(new HashMap<>());
+  }
+
+  public boolean addProgramClass(DexProgramClass clazz) {
+    return packages
+        .computeIfAbsent(clazz.getType().getPackageDescriptor(), ProgramPackage::new)
+        .add(clazz);
+  }
+
+  public boolean isEmpty() {
+    return packages.isEmpty();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/SyntheticItems.java b/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
deleted file mode 100644
index 3df048f..0000000
--- a/src/main/java/com/android/tools/r8/graph/SyntheticItems.java
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph;
-
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
-import com.google.common.collect.ImmutableSet;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-
-public class SyntheticItems implements SyntheticDefinitionsProvider {
-
-  public static class CommittedItems implements SyntheticDefinitionsProvider {
-    // Set of all types that represent synthesized items.
-    private final ImmutableSet<DexType> syntheticTypes;
-
-    private CommittedItems(ImmutableSet<DexType> syntheticTypes) {
-      this.syntheticTypes = syntheticTypes;
-    }
-
-    SyntheticItems toSyntheticItems() {
-      return new SyntheticItems(syntheticTypes);
-    }
-
-    @Override
-    public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
-      return baseDefinitionFor.apply(type);
-    }
-  }
-
-  // Thread safe collection of synthesized classes that are not yet committed to the application.
-  private final Map<DexType, DexProgramClass> pendingClasses = new ConcurrentHashMap<>();
-
-  // Immutable set of types that represent synthetic definitions in the application (eg, committed).
-  private final ImmutableSet<DexType> syntheticTypes;
-
-  private SyntheticItems(ImmutableSet<DexType> syntheticTypes) {
-    this.syntheticTypes = syntheticTypes;
-  }
-
-  public static SyntheticItems createInitialSyntheticItems() {
-    return new SyntheticItems(ImmutableSet.of());
-  }
-
-  public boolean hasPendingSyntheticClasses() {
-    return !pendingClasses.isEmpty();
-  }
-
-  public Collection<DexProgramClass> getPendingSyntheticClasses() {
-    return Collections.unmodifiableCollection(pendingClasses.values());
-  }
-
-  @Override
-  public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
-    DexProgramClass pending = pendingClasses.get(type);
-    if (pending != null) {
-      assert baseDefinitionFor.apply(type) == null
-          : "Pending synthetic definition also present in the active program: " + type;
-      return pending;
-    }
-    return baseDefinitionFor.apply(type);
-  }
-
-  // TODO(b/158159959): Remove the usage of this direct class addition (and name-based id).
-  public void addSyntheticClass(DexProgramClass clazz) {
-    assert clazz.type.isD8R8SynthesizedClassType();
-    assert !syntheticTypes.contains(clazz.type);
-    DexProgramClass previous = pendingClasses.put(clazz.type, clazz);
-    assert previous == null || previous == clazz;
-  }
-
-  public boolean isSyntheticClass(DexType type) {
-    return syntheticTypes.contains(type)
-        || pendingClasses.containsKey(type)
-        // TODO(b/158159959): Remove usage of name-based identification.
-        || type.isD8R8SynthesizedClassType();
-  }
-
-  public boolean isSyntheticClass(DexProgramClass clazz) {
-    return isSyntheticClass(clazz.type);
-  }
-
-  public CommittedItems commit(DexApplication application) {
-    assert verifyAllPendingSyntheticsAreInApp(application, this);
-    // All synthetics are in the app proper and no further meta-data is present so the empty
-    // collection is currently returned here.
-    ImmutableSet<DexType> merged = syntheticTypes;
-    if (!pendingClasses.isEmpty()) {
-      merged =
-          ImmutableSet.<DexType>builder()
-              .addAll(syntheticTypes)
-              .addAll(pendingClasses.keySet())
-              .build();
-    }
-    return new CommittedItems(merged);
-  }
-
-  public CommittedItems commit(DexApplication application, NestedGraphLens lens) {
-    return new CommittedItems(lens.rewriteTypes(commit(application).syntheticTypes));
-  }
-
-  private static boolean verifyAllPendingSyntheticsAreInApp(
-      DexApplication app, SyntheticItems synthetics) {
-    for (DexProgramClass clazz : synthetics.getPendingSyntheticClasses()) {
-      assert app.programDefinitionFor(clazz.type) != null;
-    }
-    return true;
-  }
-}
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 a938abb..8b17c78 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,12 +6,16 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
 import com.android.tools.r8.horizontalclassmerging.policies.NoFields;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInternalUtilityClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
 import com.android.tools.r8.horizontalclassmerging.policies.NoRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoStaticClassInitializer;
 import com.android.tools.r8.horizontalclassmerging.policies.NotEntryPoint;
+import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
@@ -36,14 +40,18 @@
 
     List<Policy> policies =
         ImmutableList.of(
+            new NotMatchedByNoHorizontalClassMerging(appView),
             new NoFields(),
             // TODO(b/166071504): Allow merging of classes that implement interfaces.
             new NoInterfaces(),
+            new NoAnnotations(),
+            new NoInnerClasses(),
             new NoStaticClassInitializer(),
+            new NoKeepRules(appView),
             new NoRuntimeTypeChecks(classMergingEnqueuerExtension),
             new NotEntryPoint(appView.dexItemFactory()),
-            new NoInternalUtilityClasses(appView.dexItemFactory()),
-            new SameParentClass()
+            new SameParentClass(),
+            new RespectPackageBoundaries(appView)
             // TODO: add policies
             );
 
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 482ade5..b0e8d13 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -124,24 +124,40 @@
 
     /** 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;
     }
 
     /** Unidirectional mapping from one method to another. */
     public Builder recordExtraOriginalSignature(DexMethod from, DexMethod to) {
+      if (from == to) {
+        return this;
+      }
+
       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);
@@ -151,8 +167,10 @@
             .getOrDefault(existingFrom, Collections.emptySet())
             .isEmpty();
       }
+
       methodMap.put(from, to);
       completeInverseMethodMap.computeIfAbsent(to, ignore -> new HashSet<>()).add(from);
+
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index 1ce2ee4..5c67423 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import java.util.ArrayList;
 import java.util.Collection;
 
 public abstract class MultiClassPolicy extends Policy {
@@ -13,6 +14,7 @@
    * Remove all groups containing no or only a single class, as there is no point in merging these.
    */
   protected void removeTrivialGroups(Collection<Collection<DexProgramClass>> groups) {
+    assert !(groups instanceof ArrayList);
     groups.removeIf(group -> group.size() < 2);
   }
 
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 9ec4e5f..322958f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.stream.Collectors;
 
 /**
@@ -20,18 +21,12 @@
   }
 
   // TODO(b/165506334): if performing mutable operation ensure that linked lists are used
-  private Collection<Collection<DexProgramClass>> applySingleClassPolicy(
-      SingleClassPolicy policy, Collection<Collection<DexProgramClass>> groups) {
+  private LinkedList<Collection<DexProgramClass>> applySingleClassPolicy(
+      SingleClassPolicy policy, LinkedList<Collection<DexProgramClass>> groups) {
     Iterator<Collection<DexProgramClass>> i = groups.iterator();
     while (i.hasNext()) {
       Collection<DexProgramClass> group = i.next();
-      Iterator<DexProgramClass> j = group.iterator();
-      while (j.hasNext()) {
-        DexProgramClass clazz = j.next();
-        if (!policy.canMerge(clazz)) {
-          j.remove();
-        }
-      }
+      group.removeIf(clazz -> !policy.canMerge(clazz));
       if (group.size() < 2) {
         i.remove();
       }
@@ -39,28 +34,36 @@
     return groups;
   }
 
-  private Collection<Collection<DexProgramClass>> applyMultiClassPolicy(
-      MultiClassPolicy policy, Collection<Collection<DexProgramClass>> groups) {
+  private LinkedList<Collection<DexProgramClass>> applyMultiClassPolicy(
+      MultiClassPolicy policy, LinkedList<Collection<DexProgramClass>> groups) {
     // For each group apply the multi class policy and add all the new groups together.
     return groups.stream()
         .flatMap(group -> policy.apply(group).stream())
-        .collect(Collectors.toList());
+        .collect(Collectors.toCollection(LinkedList::new));
   }
 
   @Override
   public Collection<Collection<DexProgramClass>> run(
-      Collection<Collection<DexProgramClass>> groups) {
+      Collection<Collection<DexProgramClass>> inputGroups) {
+    LinkedList<Collection<DexProgramClass>> linkedGroups;
+
+    if (inputGroups instanceof LinkedList) {
+      linkedGroups = (LinkedList<Collection<DexProgramClass>>) inputGroups;
+    } else {
+      linkedGroups = new LinkedList<>(inputGroups);
+    }
+
     for (Policy policy : policies) {
       if (policy instanceof SingleClassPolicy) {
-        groups = applySingleClassPolicy((SingleClassPolicy) policy, groups);
+        linkedGroups = applySingleClassPolicy((SingleClassPolicy) policy, linkedGroups);
       } else if (policy instanceof MultiClassPolicy) {
-        groups = applyMultiClassPolicy((MultiClassPolicy) policy, groups);
+        linkedGroups = applyMultiClassPolicy((MultiClassPolicy) policy, linkedGroups);
       }
 
       // Any policy should not return any trivial groups.
-      assert groups.stream().allMatch(group -> group.size() >= 2);
+      assert linkedGroups.stream().allMatch(group -> group.size() >= 2);
     }
 
-    return groups;
+    return linkedGroups;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
new file mode 100644
index 0000000..f96a787
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+
+public class NoAnnotations extends SingleClassPolicy {
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !program.hasClassOrMemberAnnotations();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java
new file mode 100644
index 0000000..ddb2aef
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.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.horizontalclassmerging.SingleClassPolicy;
+
+public class NoInnerClasses extends SingleClassPolicy {
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return program.getInnerClasses().isEmpty();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInternalUtilityClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInternalUtilityClasses.java
deleted file mode 100644
index 2887849..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInternalUtilityClasses.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import java.util.Collections;
-import java.util.Set;
-
-public class NoInternalUtilityClasses extends SingleClassPolicy {
-  private final Set<DexType> internalUtilityClasses;
-
-  public NoInternalUtilityClasses(DexItemFactory dexItemFactory) {
-    this.internalUtilityClasses = Collections.singleton(dexItemFactory.enumUnboxingUtilityType);
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    return !internalUtilityClasses.contains(program.type);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
new file mode 100644
index 0000000..6181c41
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -0,0 +1,30 @@
+// 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.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Iterables;
+
+public class NoKeepRules extends SingleClassPolicy {
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public NoKeepRules(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    DexType type = program.getType();
+    boolean anyPinned =
+        appView.appInfo().isPinned(type)
+            || Iterables.any(
+                program.members(), member -> appView.appInfo().isPinned(member.toReference()));
+    return !anyPinned;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
new file mode 100644
index 0000000..fbd17f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -0,0 +1,25 @@
+// 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.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Set;
+
+public class NotMatchedByNoHorizontalClassMerging extends SingleClassPolicy {
+  private final Set<DexType> neverMergeClassHorizontally;
+
+  public NotMatchedByNoHorizontalClassMerging(AppView<AppInfoWithLiveness> appView) {
+    neverMergeClassHorizontally = appView.appInfo().getNoHorizontalClassMergingSet();
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !neverMergeClassHorizontally.contains(program.toReference());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
new file mode 100644
index 0000000..4b112b8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -0,0 +1,102 @@
+// 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.AppView;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+public class RespectPackageBoundaries extends MultiClassPolicy {
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public RespectPackageBoundaries(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  boolean shouldRestrictMergingAcrossPackageBoundary(DexProgramClass clazz) {
+    // Check that the class is public, otherwise it is package private.
+    if (!clazz.isPublic()) {
+      return true;
+    }
+
+    // If any members are package private or protected, then their access depends on the package.
+    for (DexEncodedMember<?, ?> member : clazz.members()) {
+      if (member.getAccessFlags().isPackagePrivateOrProtected()) {
+        return true;
+      }
+    }
+
+    // Check that all accesses from [clazz] to classes or members from the current package of
+    // [clazz] will continue to work. This is guaranteed if the methods of [clazz] do not access
+    // any private or protected classes or members from the current package of [clazz].
+    IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
+    TraversalContinuation result =
+        clazz.traverseProgramMethods(
+            method -> {
+              registry.setContext(method);
+              method.registerCodeReferences(registry);
+              if (registry.foundIllegalAccess()) {
+                return TraversalContinuation.BREAK;
+              }
+              return TraversalContinuation.CONTINUE;
+            });
+    return result.shouldBreak();
+  }
+
+  /** Sort unrestricted classes into restricted classes if they are in the same package. */
+  void tryFindRestrictedPackage(
+      LinkedList<DexProgramClass> unrestrictedClasses,
+      Map<String, Collection<DexProgramClass>> restrictedClasses) {
+    Iterator<DexProgramClass> i = unrestrictedClasses.iterator();
+    while (i.hasNext()) {
+      DexProgramClass clazz = i.next();
+      Collection<DexProgramClass> restrictedPackage =
+          restrictedClasses.get(clazz.type.getPackageDescriptor());
+      if (restrictedPackage != null) {
+        restrictedPackage.add(clazz);
+        i.remove();
+      }
+    }
+  }
+
+  @Override
+  public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
+    Map<String, Collection<DexProgramClass>> restrictedClasses = new LinkedHashMap<>();
+    LinkedList<DexProgramClass> unrestrictedClasses = new LinkedList<>();
+
+    // Sort all restricted classes into packages.
+    for (DexProgramClass clazz : group) {
+      if (shouldRestrictMergingAcrossPackageBoundary(clazz)) {
+        restrictedClasses
+            .computeIfAbsent(clazz.type.getPackageDescriptor(), ignore -> new ArrayList<>())
+            .add(clazz);
+      } else {
+        unrestrictedClasses.add(clazz);
+      }
+    }
+
+    tryFindRestrictedPackage(unrestrictedClasses, restrictedClasses);
+    removeTrivialGroups(restrictedClasses.values());
+
+    // TODO(b/166577694): Add the unrestricted classes to restricted groups, but ensure they aren't
+    // the merge target.
+    Collection<Collection<DexProgramClass>> groups = new ArrayList<>(restrictedClasses.size() + 1);
+    if (unrestrictedClasses.size() > 1) {
+      groups.add(unrestrictedClasses);
+    }
+    groups.addAll(restrictedClasses.values());
+    return groups;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 8d33495..1e93d1b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -107,7 +107,10 @@
   private void enqueueMethodsForReprocessing(
       AppInfoWithLiveness appInfo, ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(appInfo.classes(), this::processClass, executorService);
-    ThreadUtils.processItems(appInfo.synthesizedClasses(), this::processClass, executorService);
+    ThreadUtils.processItems(
+        appInfo.getSyntheticItems().getPendingSyntheticClasses(),
+        this::processClass,
+        executorService);
     postMethodProcessorBuilder.put(methodsToReprocess);
   }
 
@@ -145,7 +148,8 @@
 
   private static boolean verifyNoConstantFieldsOnSynthesizedClasses(
       AppView<AppInfoWithLiveness> appView) {
-    for (DexProgramClass clazz : appView.appInfo().synthesizedClasses()) {
+    for (DexProgramClass clazz :
+        appView.appInfo().getSyntheticItems().getPendingSyntheticClasses()) {
       for (DexEncodedField field : clazz.fields()) {
         assert field.getOptimizationInfo().getAbstractValue().isUnknown();
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 96cdb99..c374875 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -264,11 +264,19 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       PredicateSet<DexType> alwaysClassInline,
-      Set<DexType> neverMerge,
+      Set<DexType> neverMergeClassVertically,
+      Set<DexType> neverMergeClassHorizontally,
+      Set<DexType> neverMergeStaticClassHorizontally,
       Set<DexMethod> alwaysInline,
       Set<DexMethod> bypassClinitforInlining) {
     new RootSetExtension(
-            appView, alwaysClassInline, neverMerge, alwaysInline, bypassClinitforInlining)
+            appView,
+            alwaysClassInline,
+            neverMergeClassVertically,
+            neverMergeClassHorizontally,
+            neverMergeStaticClassHorizontally,
+            alwaysInline,
+            bypassClinitforInlining)
         .extend(subtypingInfo);
   }
 
@@ -383,7 +391,9 @@
     private final ProtoReferences references;
 
     private final PredicateSet<DexType> alwaysClassInline;
-    private final Set<DexType> neverMerge;
+    private final Set<DexType> neverMergeClassVertically;
+    private final Set<DexType> neverMergeClassHorizontally;
+    private final Set<DexType> neverMergeStaticClassHorizontally;
 
     private final Set<DexMethod> alwaysInline;
     private final Set<DexMethod> bypassClinitforInlining;
@@ -391,13 +401,17 @@
     RootSetExtension(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         PredicateSet<DexType> alwaysClassInline,
-        Set<DexType> neverMerge,
+        Set<DexType> neverMergeClassVertically,
+        Set<DexType> neverMergeClassHorizontally,
+        Set<DexType> neverMergeStaticClassHorizontally,
         Set<DexMethod> alwaysInline,
         Set<DexMethod> bypassClinitforInlining) {
       this.appView = appView;
       this.references = appView.protoShrinker().references;
       this.alwaysClassInline = alwaysClassInline;
-      this.neverMerge = neverMerge;
+      this.neverMergeClassVertically = neverMergeClassVertically;
+      this.neverMergeClassHorizontally = neverMergeClassHorizontally;
+      this.neverMergeStaticClassHorizontally = neverMergeStaticClassHorizontally;
       this.alwaysInline = alwaysInline;
       this.bypassClinitforInlining = bypassClinitforInlining;
     }
@@ -451,14 +465,20 @@
       // For consistency, never merge the GeneratedMessageLite builders. These will only have a
       // unique subtype if the application has a single proto message, which mostly happens during
       // testing.
-      neverMerge.add(references.generatedMessageLiteBuilderType);
-      neverMerge.add(references.generatedMessageLiteExtendableBuilderType);
+      neverMergeClass(references.generatedMessageLiteBuilderType);
+      neverMergeClass(references.generatedMessageLiteExtendableBuilderType);
     }
 
     private void neverMergeMessageLite() {
       // MessageLite is used in several signatures that we use for recognizing methods, so don't
       // allow it to me merged.
-      neverMerge.add(references.messageLiteType);
+      neverMergeClass(references.messageLiteType);
+    }
+
+    private void neverMergeClass(DexType type) {
+      neverMergeClassVertically.add(type);
+      neverMergeClassHorizontally.add(type);
+      neverMergeStaticClassHorizontally.add(type);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 7240424..8f4baa6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -87,9 +87,7 @@
       DexClass clazz = appView.definitionFor(type);
       return clazz != null
           && clazz.isResolvable(appView)
-          && AccessControl.isClassAccessible(
-                  clazz, context.getHolder(), appView.options().featureSplitConfiguration)
-              .isTrue();
+          && AccessControl.isClassAccessible(clazz, context, appView).isTrue();
     }
     assert baseType.isPrimitiveType();
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 35767cc..7085e80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -108,6 +108,7 @@
     if (type.isPrimitiveType()) {
       return true;
     }
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
       DexClass definition = appView.definitionFor(baseType);
@@ -116,11 +117,11 @@
         return true;
       }
       // Check that the class is accessible.
-      if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
+      if (AccessControl.isClassAccessible(definition, context, appViewWithLiveness)
+          .isPossiblyFalse()) {
         return true;
       }
     }
-    AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     TypeElement castType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     if (object()
         .getDynamicUpperBoundType(appViewWithLiveness)
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index d28135d..86101ba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -114,13 +114,16 @@
       return true;
     }
 
+    assert appView.appInfo().hasLiveness();
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
     DexClass clazz = appView.definitionFor(baseType);
     // * Check that the class and its super types are present.
     if (clazz == null || !clazz.isResolvable(appView)) {
       return true;
     }
     // * Check that the class is accessible.
-    if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(clazz, context, appViewWithLiveness).isPossiblyFalse()) {
       return true;
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index d24dce8..22e4c4e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -94,13 +94,17 @@
 
   @Override
   public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+    // We only use InitClass instructions in R8.
+    assert appView.enableWholeProgramOptimizations();
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     DexClass definition = appView.definitionFor(clazz);
     // * Check that the class is present.
     if (definition == null) {
       return true;
     }
     // * Check that the class is accessible.
-    if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(definition, context, appViewWithLiveness)
+        .isPossiblyFalse()) {
       return true;
     }
     if (clazz.classInitializationMayHaveSideEffects(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 314317e..063592f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.LongInterval;
 import java.util.List;
 
@@ -133,6 +134,9 @@
       return true;
     }
 
+    assert appView.appInfo().hasLiveness();
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
     // Check if the type is guaranteed to be present.
     DexClass clazz = appView.definitionFor(baseType);
     if (clazz == null) {
@@ -145,7 +149,7 @@
     }
 
     // Check if the type is guaranteed to be accessible.
-    if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(clazz, context, appViewWithLiveness).isPossiblyFalse()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 2783471..25a4b47 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 
 public class InvokeNewArray extends Invoke {
@@ -160,6 +161,9 @@
       return true;
     }
 
+    assert appView.appInfo().hasLiveness();
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
     // Check if the type is guaranteed to be present.
     DexClass clazz = appView.definitionFor(baseType);
     if (clazz == null) {
@@ -173,7 +177,7 @@
     }
 
     // Check if the type is guaranteed to be accessible.
-    if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(clazz, context, appViewWithLiveness).isPossiblyFalse()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 3b8fc7f..ff7a054 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -169,7 +169,8 @@
     }
 
     // Verify that the instruction does not lead to an IllegalAccessError.
-    if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(definition, context, appViewWithLiveness)
+        .isPossiblyFalse()) {
       return true;
     }
 
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 5107d27..605f698 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
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import static com.android.tools.r8.graph.DexAnnotation.readAnnotationSynthesizedClassMap;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
@@ -13,7 +12,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -90,7 +88,6 @@
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
-import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -105,22 +102,16 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Suppliers;
-import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ListMultimap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.TreeSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -237,8 +228,8 @@
       this.desugaredLibraryAPIConverter =
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
       this.backportedMethodRewriter =
-          options.testing.forceLibBackportsInL8CfToCf
-              ? new BackportedMethodRewriter(appView, this)
+          options.cfToCfDesugar || options.testing.forceLibBackportsInL8CfToCf
+              ? new BackportedMethodRewriter(appView)
               : null;
       this.twrCloseResourceRewriter = null;
       this.lambdaMerger = null;
@@ -276,7 +267,7 @@
         ((options.desugarState == DesugarState.ON) && enableTwrCloseResourceDesugaring())
             ? new TwrCloseResourceRewriter(appView, this)
             : null;
-    this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
+    this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
     this.desugaredLibraryRetargeter =
         options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
             ? null
@@ -445,10 +436,10 @@
     }
   }
 
-  private void synthesizeJava8UtilityClass(
-      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
+  private void processSynthesizedJava8UtilityClasses(ExecutorService executorService)
+      throws ExecutionException {
     if (backportedMethodRewriter != null) {
-      backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
+      backportedMethodRewriter.processSynthesizedClasses(this, executorService);
     }
   }
 
@@ -459,10 +450,10 @@
     }
   }
 
-  private void synthesizeEnumUnboxingUtilityMethods(
-      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
+  private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
+      throws ExecutionException {
     if (enumUnboxer != null) {
-      enumUnboxer.synthesizeUtilityMethods(builder, this, executorService);
+      enumUnboxer.synthesizeUtilityMethods(this, executorService);
     }
   }
 
@@ -489,96 +480,19 @@
     synthesizeLambdaClasses(builder, executor);
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     synthesizeTwrCloseResourceUtilityClass(builder, executor);
-    synthesizeJava8UtilityClass(builder, executor);
+    processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
 
     processCovariantReturnTypeAnnotations(builder);
     generateDesugaredLibraryAPIWrappers(builder, executor);
 
-    handleSynthesizedClassMapping(appView, builder);
     timing.end();
 
     DexApplication app = builder.build();
     appView.setAppInfo(
         new AppInfo(
-            app,
-            appView.appInfo().getMainDexClasses(),
-            appView.appInfo().getSyntheticItems().commit(app)));
-  }
-
-  private void handleSynthesizedClassMapping(AppView<?> appView, Builder<?> builder) {
-    if (options.intermediate) {
-      updateSynthesizedClassMapping(builder);
-    }
-
-    updateMainDexListWithSynthesizedClassMap(appView, builder);
-
-    if (!options.intermediate) {
-      clearSynthesizedClassMapping(builder);
-    }
-  }
-
-  private void updateMainDexListWithSynthesizedClassMap(AppView<?> appView, Builder<?> builder) {
-    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
-    if (mainDexClasses.isEmpty()) {
-      return;
-    }
-    Map<DexType, DexProgramClass> programClasses =
-        builder.getProgramClasses().stream()
-            .collect(Collectors.toMap(programClass -> programClass.type, Function.identity()));
-    List<DexProgramClass> newMainDexClasses = new ArrayList<>();
-    mainDexClasses.forEach(
-        mainDexClass -> {
-          DexProgramClass definition = programClasses.get(mainDexClass);
-          if (definition == null) {
-            return;
-          }
-          Iterable<DexType> syntheticTypes =
-              readAnnotationSynthesizedClassMap(definition, appView.dexItemFactory());
-          for (DexType syntheticType : syntheticTypes) {
-            DexProgramClass syntheticClass = programClasses.get(syntheticType);
-            if (syntheticClass != null) {
-              newMainDexClasses.add(syntheticClass);
-            }
-          }
-        });
-    mainDexClasses.addAll(newMainDexClasses);
-  }
-
-  private void clearSynthesizedClassMapping(Builder<?> builder) {
-    for (DexProgramClass clazz : builder.getProgramClasses()) {
-      clazz.setAnnotations(
-          clazz.annotations().getWithout(builder.dexItemFactory.annotationSynthesizedClassMap));
-    }
-  }
-
-  private void updateSynthesizedClassMapping(Builder<?> builder) {
-    ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
-        ArrayListMultimap.create();
-    for (DexProgramClass synthesized : builder.getSynthesizedClasses()) {
-      for (DexProgramClass original : synthesized.getSynthesizedFrom()) {
-        originalToSynthesized.put(original, synthesized);
-      }
-    }
-
-    for (Map.Entry<DexProgramClass, Collection<DexProgramClass>> entry :
-        originalToSynthesized.asMap().entrySet()) {
-      DexProgramClass original = entry.getKey();
-      // Use a tree set to make sure that we have an ordering on the types.
-      // These types are put in an array in annotations in the output and we
-      // need a consistent ordering on them.
-      TreeSet<DexType> synthesized = new TreeSet<>(DexType::slowCompareTo);
-      entry.getValue()
-          .stream()
-          .map(dexProgramClass -> dexProgramClass.type)
-          .forEach(synthesized::add);
-      synthesized.addAll(readAnnotationSynthesizedClassMap(original, builder.dexItemFactory));
-
-      DexAnnotation updatedAnnotation =
-          DexAnnotation.createAnnotationSynthesizedClassMap(synthesized, builder.dexItemFactory);
-
-      original.setAnnotations(original.annotations().getWithAddedOrReplaced(updatedAnnotation));
-    }
+            appView.appInfo().getSyntheticItems().commit(app),
+            appView.appInfo().getMainDexClasses()));
   }
 
   private void convertMethods(DexProgramClass clazz) {
@@ -675,16 +589,9 @@
         executorService);
   }
 
-  public DexApplication optimize() throws ExecutionException {
-    ExecutorService executor = Executors.newSingleThreadExecutor();
-    try {
-      return optimize(executor);
-    } finally {
-      executor.shutdown();
-    }
-  }
-
-  public DexApplication optimize(ExecutorService executorService) throws ExecutionException {
+  public DexApplication optimize(
+      AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
+      throws ExecutionException {
     DexApplication application = appView.appInfo().app();
 
     computeReachabilitySensitivity(application);
@@ -804,10 +711,9 @@
 
     printPhase("Utility classes synthesis");
     synthesizeTwrCloseResourceUtilityClass(builder, executorService);
-    synthesizeJava8UtilityClass(builder, executorService);
+    processSynthesizedJava8UtilityClasses(executorService);
     synthesizeRetargetClass(builder, executorService);
-    handleSynthesizedClassMapping(appView, builder);
-    synthesizeEnumUnboxingUtilityMethods(builder, executorService);
+    synthesizeEnumUnboxingUtilityMethods(executorService);
 
     printPhase("Lambda merging finalization");
     // TODO(b/127694949): Adapt to PostOptimization.
@@ -885,14 +791,20 @@
 
     // Assure that no more optimization feedback left after post processing.
     assert feedback.noUpdatesLeft();
-
-    // Check if what we've added to the application builder as synthesized classes are same as
-    // what we've added and used through AppInfo.
-    assert appView.appInfo().synthesizedClasses().containsAll(builder.getSynthesizedClasses())
-        && builder.getSynthesizedClasses().containsAll(appView.appInfo().synthesizedClasses());
+    assert checkLegacySyntheticsAreInBuilder(appView, builder);
     return builder.build();
   }
 
+  private boolean checkLegacySyntheticsAreInBuilder(
+      AppView<AppInfoWithLiveness> appView, Builder<?> builder) {
+    Collection<DexProgramClass> inAppInfo =
+        appView.appInfo().getSyntheticItems().getLegacyPendingClasses();
+    Collection<DexProgramClass> inBuilder = builder.getSynthesizedClasses();
+    assert inAppInfo.containsAll(inBuilder);
+    assert inBuilder.containsAll(inAppInfo);
+    return true;
+  }
+
   private void waveStart(ProgramMethodSet wave) {
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 5c08f1f..8cc0ffa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -7,27 +7,17 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 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.DexProgramClass.ChecksumSupplier;
 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.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -45,7 +35,6 @@
 import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
-import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -54,13 +43,13 @@
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
@@ -72,18 +61,13 @@
   public static final String UTILITY_CLASS_NAME_PREFIX = "$r8$backportedMethods$utility";
 
   private final AppView<?> appView;
-  private final IRConverter converter;
-  private final DexItemFactory factory;
   private final RewritableMethods rewritableMethods;
   private final boolean enabled;
 
-  private final Set<DexType> holders = Sets.newConcurrentHashSet();
-  private final Map<DexMethod, MethodProvider> methodProviders = new ConcurrentHashMap<>();
+  private final Queue<ProgramMethod> synthesizedMethods = new ConcurrentLinkedQueue<>();
 
-  public BackportedMethodRewriter(AppView<?> appView, IRConverter converter) {
+  public BackportedMethodRewriter(AppView<?> appView) {
     this.appView = appView;
-    this.converter = converter;
-    this.factory = appView.dexItemFactory();
     this.rewritableMethods = new RewritableMethods(appView.options(), appView);
     // Disable rewriting if there are no methods to rewrite or if the API level is higher than
     // the highest known API level when the compiler is built. This ensures that when this is used
@@ -128,7 +112,6 @@
     if (!enabled) {
       return; // Nothing to do!
     }
-
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
@@ -143,17 +126,11 @@
         continue;
       }
 
-      MethodProvider provider = getMethodProviderOrNull(invoke.getInvokedMethod());
-      if (provider == null) {
-        continue;
-      }
-
-      provider.rewriteInvoke(invoke, iterator, code, appView, affectedValues);
-
-      if (provider.requiresGenerationOfCode()) {
-        DexMethod newMethod = provider.provideMethod(appView);
-        methodProviders.putIfAbsent(newMethod, provider);
-        holders.add(code.method().holder());
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      MethodProvider provider = getMethodProviderOrNull(invokedMethod);
+      if (provider != null) {
+        provider.rewriteInvoke(
+            invoke, iterator, code, appView, affectedValues, synthesizedMethods::add);
       }
     }
     if (!affectedValues.isEmpty()) {
@@ -161,125 +138,15 @@
     }
   }
 
-  private Collection<DexProgramClass> findSynthesizedFrom(Builder<?> builder, DexType holder) {
-    for (DexProgramClass synthesizedClass : builder.getSynthesizedClasses()) {
-      if (holder == synthesizedClass.getType()) {
-        return synthesizedClass.getSynthesizedFrom();
-      }
-    }
-    return null;
-  }
-
-  public static boolean hasRewrittenMethodPrefix(DexType clazz) {
-    return clazz.descriptor.toString().contains(UTILITY_CLASS_NAME_PREFIX);
-  }
-
-  public void synthesizeUtilityClasses(Builder<?> builder, ExecutorService executorService)
+  public void processSynthesizedClasses(IRConverter converter, ExecutorService executor)
       throws ExecutionException {
-    if (!enabled) {
-      return;
-    }
-    if (holders.isEmpty()) {
-      return;
-    }
-    // Compute referencing classes ignoring references in-between utility classes.
-    Set<DexProgramClass> referencingClasses = Sets.newConcurrentHashSet();
-    for (DexType holder : holders) {
-      DexClass definitionFor = appView.definitionFor(holder);
-      if (definitionFor == null) {
-        Collection<DexProgramClass> synthesizedFrom = findSynthesizedFrom(builder, holder);
-        assert synthesizedFrom != null;
-        referencingClasses.addAll(synthesizedFrom);
-      } else {
-        referencingClasses.add(definitionFor.asProgramClass());
+    while (!synthesizedMethods.isEmpty()) {
+      List<ProgramMethod> methods = new ArrayList<>(synthesizedMethods);
+      synthesizedMethods.clear();
+      for (ProgramMethod method : methods) {
+        converter.optimizeSynthesizedClass(method.getHolder(), executor);
       }
     }
-
-    MethodAccessFlags flags =
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
-    ClassAccessFlags classAccessFlags =
-        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
-    // Generate the utility classes in a loop since utility classes can require the
-    // the creation of other utility classes.
-    // Function multiplyExact(long, int) calls multiplyExact(long, long) for example.
-    while (!methodProviders.isEmpty()) {
-      DexMethod method = methodProviders.keySet().iterator().next();
-      MethodProvider provider = methodProviders.remove(method);
-      assert provider.requiresGenerationOfCode();
-      // The utility class could have been synthesized, e.g., running R8 then D8,
-      // or if already processed in this while loop.
-      if (appView.definitionFor(method.holder) != null) {
-        continue;
-      }
-      Code code = provider.generateTemplateMethod(appView.options(), method);
-      DexEncodedMethod dexEncodedMethod =
-          new DexEncodedMethod(
-              method,
-              flags,
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              code,
-              true);
-      boolean addToMainDexClasses =
-          appView.appInfo().getMainDexClasses().containsAnyOf(referencingClasses);
-      DexProgramClass utilityClass =
-          synthesizeClassWithUniqueMethod(
-              builder,
-              classAccessFlags,
-              method.holder,
-              dexEncodedMethod,
-              "java8 methods utility class",
-              addToMainDexClasses,
-              appView);
-      // The following may add elements to methodsProviders.
-      converter.optimizeSynthesizedClass(utilityClass, executorService);
-    }
-  }
-
-  static DexProgramClass synthesizeClassWithUniqueMethod(
-      Builder<?> builder,
-      ClassAccessFlags accessFlags,
-      DexType type,
-      DexEncodedMethod uniqueMethod,
-      String origin,
-      boolean addToMainDexClasses,
-      AppView<?> appView) {
-    DexItemFactory factory = appView.dexItemFactory();
-    DexProgramClass newClass =
-        new DexProgramClass(
-            type,
-            null,
-            new SynthesizedOrigin(origin, BackportedMethodRewriter.class),
-            accessFlags,
-            factory.objectType,
-            DexTypeList.empty(),
-            null,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            uniqueMethod.isStatic()
-                ? new DexEncodedMethod[] {uniqueMethod}
-                : DexEncodedMethod.EMPTY_ARRAY,
-            uniqueMethod.isStatic()
-                ? DexEncodedMethod.EMPTY_ARRAY
-                : new DexEncodedMethod[] {uniqueMethod},
-            factory.getSkipNameValidationForTesting(),
-            getChecksumSupplier(uniqueMethod, appView));
-    appView.appInfo().addSynthesizedClass(newClass, addToMainDexClasses);
-    builder.addSynthesizedClass(newClass);
-    return newClass;
-  }
-
-  private static ChecksumSupplier getChecksumSupplier(DexEncodedMethod method, AppView<?> appView) {
-    if (!appView.options().encodeChecksums) {
-      return DexProgramClass::invalidChecksumRequest;
-    }
-    return c -> method.method.hashCode();
   }
 
   private MethodProvider getMethodProviderOrNull(DexMethod method) {
@@ -1433,13 +1300,8 @@
         InstructionListIterator iterator,
         IRCode code,
         AppView<?> appView,
-        Set<Value> affectedValues);
-
-    public abstract DexMethod provideMethod(AppView<?> appView);
-
-    public abstract Code generateTemplateMethod(InternalOptions options, DexMethod method);
-
-    public abstract boolean requiresGenerationOfCode();
+        Set<Value> affectedValues,
+        Consumer<ProgramMethod> registerSynthesizedMethod);
   }
 
   private static final class InvokeRewriter extends MethodProvider {
@@ -1457,32 +1319,17 @@
         InstructionListIterator iterator,
         IRCode code,
         AppView<?> appView,
-        Set<Value> affectedValues) {
+        Set<Value> affectedValues,
+        Consumer<ProgramMethod> registerSynthesizedMethod) {
       rewriter.rewrite(invoke, iterator, appView.dexItemFactory(), affectedValues);
       assert code.isConsistentSSA();
     }
-
-    @Override
-    public boolean requiresGenerationOfCode() {
-      return false;
-    }
-
-    @Override
-    public DexMethod provideMethod(AppView<?> appView) {
-      throw new Unreachable();
-    }
-
-    @Override
-    public Code generateTemplateMethod(InternalOptions options, DexMethod method) {
-      throw new Unreachable();
-    }
   }
 
   private static class MethodGenerator extends MethodProvider {
 
     private final TemplateMethodFactory factory;
     private final String methodName;
-    DexMethod generatedMethod;
 
     MethodGenerator(DexMethod method, TemplateMethodFactory factory) {
       this(method, factory, method.name.toString());
@@ -1500,44 +1347,39 @@
         InstructionListIterator iterator,
         IRCode code,
         AppView<?> appView,
-        Set<Value> affectedValues) {
+        Set<Value> affectedValues,
+        Consumer<ProgramMethod> registerSynthesizedMethod) {
+      ProgramMethod method =
+          appView
+              .getSyntheticItems()
+              .createMethod(
+                  code.context().getHolder(),
+                  appView.dexItemFactory(),
+                  builder ->
+                      builder
+                          .setProto(getProto(appView.dexItemFactory()))
+                          .setAccessFlags(
+                              MethodAccessFlags.fromSharedAccessFlags(
+                                  Constants.ACC_PUBLIC
+                                      | Constants.ACC_STATIC
+                                      | Constants.ACC_SYNTHETIC,
+                                  false))
+                          .setCode(
+                              methodSig -> generateTemplateMethod(appView.options(), methodSig)));
+
       iterator.replaceCurrentInstruction(
-          new InvokeStatic(provideMethod(appView), invoke.outValue(), invoke.inValues()));
+          new InvokeStatic(method.getReference(), invoke.outValue(), invoke.inValues()));
+
+      registerSynthesizedMethod.accept(method);
     }
 
-    @Override
-    public DexMethod provideMethod(AppView<?> appView) {
-      if (generatedMethod != null) {
-        return generatedMethod;
-      }
-      DexItemFactory factory = appView.dexItemFactory();
-      String unqualifiedName = method.holder.getName();
-      // Avoid duplicate class names between core lib dex file and program dex files.
-      String descriptor =
-          "L"
-              + appView.options().synthesizedClassPrefix
-              + UTILITY_CLASS_NAME_PREFIX
-              + '$'
-              + unqualifiedName
-              + '$'
-              + method.proto.parameters.size()
-              + '$'
-              + methodName
-              + ';';
-      DexType type = factory.createType(descriptor);
-      generatedMethod = factory.createMethod(type, method.proto, method.name);
-      return generatedMethod;
+    public DexProto getProto(DexItemFactory itemFactory) {
+      return method.proto;
     }
 
-    @Override
     public Code generateTemplateMethod(InternalOptions options, DexMethod method) {
       return factory.create(options, method);
     }
-
-    @Override
-    public boolean requiresGenerationOfCode() {
-      return true;
-    }
   }
 
   // Specific subclass to transform virtual methods into static desugared methods.
@@ -1554,16 +1396,8 @@
     }
 
     @Override
-    public DexMethod provideMethod(AppView<?> appView) {
-      if (generatedMethod != null) {
-        return generatedMethod;
-      }
-      super.provideMethod(appView);
-      assert generatedMethod != null;
-      DexProto newProto = appView.dexItemFactory().prependTypeToProto(receiverType, method.proto);
-      generatedMethod =
-          appView.dexItemFactory().createMethod(generatedMethod.holder, newProto, method.name);
-      return generatedMethod;
+    public DexProto getProto(DexItemFactory itemFactory) {
+      return itemFactory.prependTypeToProto(receiverType, super.getProto(itemFactory));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 5ce5bb8..5280f61 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -9,15 +9,19 @@
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 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.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -27,6 +31,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Maps;
@@ -85,6 +90,49 @@
     appView.options().reporter.warning(warning);
   }
 
+  private static void synthesizeClassWithUniqueMethod(
+      Builder<?> builder,
+      ClassAccessFlags accessFlags,
+      DexType type,
+      DexEncodedMethod uniqueMethod,
+      String origin,
+      AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexProgramClass newClass =
+        new DexProgramClass(
+            type,
+            null,
+            new SynthesizedOrigin(origin, BackportedMethodRewriter.class),
+            accessFlags,
+            factory.objectType,
+            DexTypeList.empty(),
+            null,
+            null,
+            Collections.emptyList(),
+            null,
+            Collections.emptyList(),
+            DexAnnotationSet.empty(),
+            DexEncodedField.EMPTY_ARRAY,
+            DexEncodedField.EMPTY_ARRAY,
+            uniqueMethod.isStatic()
+                ? new DexEncodedMethod[] {uniqueMethod}
+                : DexEncodedMethod.EMPTY_ARRAY,
+            uniqueMethod.isStatic()
+                ? DexEncodedMethod.EMPTY_ARRAY
+                : new DexEncodedMethod[] {uniqueMethod},
+            factory.getSkipNameValidationForTesting(),
+            getChecksumSupplier(uniqueMethod, appView));
+    appView.appInfo().addSynthesizedClass(newClass, false);
+    builder.addSynthesizedClass(newClass);
+  }
+
+  private static ChecksumSupplier getChecksumSupplier(DexEncodedMethod method, AppView<?> appView) {
+    if (!appView.options().encodeChecksums) {
+      return DexProgramClass::invalidChecksumRequest;
+    }
+    return c -> method.method.hashCode();
+  }
+
   // Used by the ListOfBackportedMethods utility.
   void visit(Consumer<DexMethod> consumer) {
     retargetLibraryMember.keySet().forEach(consumer);
@@ -363,25 +411,23 @@
         DexType interfaceType = dispatchInterfaceTypeFor(emulatedDispatchMethod);
         DexEncodedMethod itfMethod =
             generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
-        BackportedMethodRewriter.synthesizeClassWithUniqueMethod(
+        synthesizeClassWithUniqueMethod(
             builder,
             itfAccessFlags,
             interfaceType,
             itfMethod,
             "desugared library dispatch interface",
-            false,
             appView);
         // Dispatch holder.
         DexType holderType = dispatchHolderTypeFor(emulatedDispatchMethod);
         DexEncodedMethod dispatchMethod =
             generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
-        BackportedMethodRewriter.synthesizeClassWithUniqueMethod(
+        synthesizeClassWithUniqueMethod(
             builder,
             holderAccessFlags,
             holderType,
             dispatchMethod,
             "desugared library dispatch class",
-            false,
             appView);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index aa15050..7f697a9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -239,11 +239,11 @@
         if (instruction.isInvokeStatic()) {
           InvokeStatic invokeStatic = instruction.asInvokeStatic();
           DexMethod method = invokeStatic.getInvokedMethod();
-          DexClass clazz = appInfo.definitionFor(method.holder);
-          if (BackportedMethodRewriter.hasRewrittenMethodPrefix(method.holder)) {
+          if (appView.getSyntheticItems().isPendingSynthetic(method.holder)) {
             // We did not create this code yet, but it will not require rewriting.
             continue;
           }
+          DexClass clazz = appInfo.definitionFor(method.holder);
           if (clazz == null) {
             // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
             // exception but we can not report it as error since it can also be the intended
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 104fc97..ecad620 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -617,7 +617,7 @@
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
+                    rewriter.lensBuilder.move(encodedMethod.method, callTarget);
 
                     DexEncodedMethod.setDebugInfoWithFakeThisParameter(
                         newMethod.getCode(), callTarget.getArity(), appView);
@@ -689,7 +689,7 @@
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
+                    rewriter.lensBuilder.move(encodedMethod.method, callTarget);
                     return newMethod;
                   });
       return new ProgramMethod(implMethodHolder, replacement);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index dd7889d..fad4980 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
+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.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -31,8 +33,6 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -63,7 +63,7 @@
 
   final DexString instanceFieldName;
 
-  final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+  final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
 
   // Maps call sites seen so far to inferred lambda descriptor. It is intended
   // to help avoid re-matching call sites we already seen. Note that same call
@@ -160,6 +160,7 @@
       appView.appInfo().addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
       builder.addSynthesizedClass(synthesizedClass);
     }
+    fixup();
     optimizeSynthesizedClasses(converter, executorService);
   }
 
@@ -345,34 +346,78 @@
     return knownLambdaClasses;
   }
 
-  public NestedGraphLens buildMappingLens(AppView<?> appView) {
-    if (originalMethodSignatures.isEmpty()) {
+  public NestedGraphLens fixup() {
+    LambdaRewriterLens lens = lensBuilder.build(appView.graphLens(), appView.dexItemFactory());
+    if (lens == null) {
       return null;
     }
-    return new LambdaRewriterLens(
-        originalMethodSignatures, appView.graphLens(), appView.dexItemFactory());
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
+      if (enclosingMethod != null) {
+        DexMethod mappedEnclosingMethod = lens.lookupMethod(enclosingMethod.getEnclosingMethod());
+        if (mappedEnclosingMethod != null
+            && mappedEnclosingMethod != enclosingMethod.getEnclosingMethod()) {
+          clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingMethod));
+        }
+      }
+    }
+    ;
+    // Return lens without method map (but still retaining originalMethodSignatures), as the
+    // generated lambdas classes are generated with the an invoke to the new method, so no
+    // code rewriting is required.
+    return lens.withoutMethodMap();
   }
 
   static class LambdaRewriterLens extends NestedGraphLens {
 
-    public LambdaRewriterLens(
+    LambdaRewriterLens(
+        Map<DexType, DexType> typeMap,
+        Map<DexMethod, DexMethod> methodMap,
+        Map<DexField, DexField> fieldMap,
+        BiMap<DexField, DexField> originalFieldSignatures,
         BiMap<DexMethod, DexMethod> originalMethodSignatures,
-        GraphLens graphLens,
-        DexItemFactory factory) {
+        GraphLens previousLens,
+        DexItemFactory dexItemFactory) {
       super(
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          ImmutableMap.of(),
-          null,
+          typeMap,
+          methodMap,
+          fieldMap,
+          originalFieldSignatures,
           originalMethodSignatures,
-          graphLens,
-          factory);
+          previousLens,
+          dexItemFactory);
     }
 
     @Override
     protected boolean isLegitimateToHaveEmptyMappings() {
       return true;
     }
+
+    private LambdaRewriterLens withoutMethodMap() {
+      methodMap.clear();
+      return this;
+    }
+
+    public static LambdaRewriterLens.Builder builder() {
+      return new LambdaRewriterLens.Builder();
+    }
+
+    public static class Builder extends NestedGraphLens.Builder {
+      public LambdaRewriterLens build(GraphLens previousLens, DexItemFactory dexItemFactory) {
+        if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
+          return null;
+        }
+        assert typeMap.isEmpty();
+        assert fieldMap.isEmpty();
+        return new LambdaRewriterLens(
+            typeMap,
+            methodMap,
+            fieldMap,
+            originalFieldSignatures,
+            originalMethodSignatures,
+            previousLens,
+            dexItemFactory);
+      }
+    }
   }
 }
-
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 996b8d8..70efbeb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1302,6 +1302,9 @@
       return;
     }
 
+    assert appView.appInfo().hasLiveness();
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
     if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) {
       return;
     }
@@ -1337,7 +1340,8 @@
       if (current.isCheckCast()) {
         boolean hasPhiUsers = current.outValue().hasPhiUsers();
         RemoveCheckCastInstructionIfTrivialResult removeResult =
-            removeCheckCastInstructionIfTrivial(current.asCheckCast(), it, code, affectedValues);
+            removeCheckCastInstructionIfTrivial(
+                appViewWithLiveness, current.asCheckCast(), it, code, affectedValues);
         if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) {
           assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
           needToRemoveTrivialPhis |= hasPhiUsers;
@@ -1346,7 +1350,8 @@
         }
       } else if (current.isInstanceOf()) {
         boolean hasPhiUsers = current.outValue().hasPhiUsers();
-        if (removeInstanceOfInstructionIfTrivial(current.asInstanceOf(), it, code)) {
+        if (removeInstanceOfInstructionIfTrivial(
+            appViewWithLiveness, current.asInstanceOf(), it, code)) {
           needToRemoveTrivialPhis |= hasPhiUsers;
         }
       }
@@ -1368,7 +1373,11 @@
 
   // Returns true if the given check-cast instruction was removed.
   private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial(
-      CheckCast checkCast, InstructionListIterator it, IRCode code, Set<Value> affectedValues) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness,
+      CheckCast checkCast,
+      InstructionListIterator it,
+      IRCode code,
+      Set<Value> affectedValues) {
     Value inValue = checkCast.object();
     Value outValue = checkCast.outValue();
     DexType castType = checkCast.getType();
@@ -1380,7 +1389,7 @@
     if (baseCastType.isClassType()) {
       DexClass baseCastClass = appView.definitionFor(baseCastType);
       if (baseCastClass == null
-          || AccessControl.isClassAccessible(baseCastClass, code.context(), appView)
+          || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness)
               .isPossiblyFalse()) {
         return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
       }
@@ -1436,7 +1445,10 @@
 
   // Returns true if the given instance-of instruction was removed.
   private boolean removeInstanceOfInstructionIfTrivial(
-      InstanceOf instanceOf, InstructionListIterator it, IRCode code) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness,
+      InstanceOf instanceOf,
+      InstructionListIterator it,
+      IRCode code) {
     ProgramMethod context = code.context();
 
     // If the instance-of type is not accessible in the current context, we should not remove the
@@ -1445,7 +1457,8 @@
     if (instanceOfBaseType.isClassType()) {
       DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType);
       if (instanceOfClass == null
-          || AccessControl.isClassAccessible(instanceOfClass, context, appView).isPossiblyFalse()) {
+          || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness)
+              .isPossiblyFalse()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index cbb86df..7a5dce9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -151,17 +152,16 @@
       return false;
     }
 
-    InternalOptions options = appView.options();
-    if (options.featureSplitConfiguration != null
-        && !options.featureSplitConfiguration.inSameFeatureOrBothInBase(singleTarget, method)) {
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(singleTarget, method)) {
       // Still allow inlining if we inline from the base into a feature.
-      if (!options.featureSplitConfiguration.isInBase(singleTarget.getHolder())) {
+      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder())) {
         whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
         return false;
       }
     }
 
-    Set<Reason> validInliningReasons = options.testing.validInliningReasons;
+    Set<Reason> validInliningReasons = appView.options().testing.validInliningReasons;
     if (validInliningReasons != null && !validInliningReasons.contains(reason)) {
       whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons);
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 611a249..a36f313 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -997,8 +997,8 @@
               AccessControl.isMethodAccessible(
                   singleTarget.getDefinition(),
                   singleTarget.getHolder().asDexClass(),
-                  context.getHolder(),
-                  appView.withClassHierarchy().appInfo());
+                  context,
+                  appView);
           if (!methodAccessible.isTrue()) {
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 7287edd..655ee2f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1284,8 +1284,7 @@
         code -> {
           ProgramMethod context = code.context();
           assert !context.getDefinition().getCode().isOutlineCode();
-          if (appView.options().featureSplitConfiguration != null
-              && appView.options().featureSplitConfiguration.isInFeature(context.getHolder())) {
+          if (appView.appInfo().getClassToFeatureSplitMap().isInFeature(context.getHolder())) {
             return;
           }
           for (BasicBlock block : code.blocks) {
@@ -1304,8 +1303,7 @@
   public void identifyOutlineSites(IRCode code) {
     ProgramMethod context = code.context();
     assert !context.getDefinition().getCode().isOutlineCode();
-    assert appView.options().featureSplitConfiguration == null
-        || !appView.options().featureSplitConfiguration.isInFeature(context.getHolder());
+    assert !appView.appInfo().getClassToFeatureSplitMap().isInFeature(context.getHolder());
     for (BasicBlock block : code.blocks) {
       new OutlineSiteIdentifier(context, block).process();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index fd270a4..f9f4d58 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -196,8 +196,7 @@
     // Don't allow the instantiated class to be in a feature, if it is, we can get a
     // NoClassDefFoundError from dalvik/art.
     if (baseClazz.isProgramClass()
-        && appView.options().featureSplitConfiguration != null
-        && appView.options().featureSplitConfiguration.isInFeature(baseClazz.asProgramClass())) {
+        && appView.appInfo().getClassToFeatureSplitMap().isInFeature(baseClazz.asProgramClass())) {
       return null;
     }
     // Make sure the (base) type is visible.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index f1bcf3e..b1fcc81 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -74,9 +74,9 @@
 
   private AtomicReference<DexProgramClass> synthesizedClass = new AtomicReference<>();
 
-  private final AppView<? extends AppInfoWithLiveness> appView;
+  private final AppView<AppInfoWithLiveness> appView;
 
-  public ServiceLoaderRewriter(AppView<? extends AppInfoWithLiveness> appView) {
+  public ServiceLoaderRewriter(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
   }
 
@@ -133,7 +133,9 @@
       }
 
       // Check that we are not service loading anything from a feature into base.
-      if (appView.appServices().hasServiceImplementationsInFeature(constClass.getValue())) {
+      if (appView
+          .appServices()
+          .hasServiceImplementationsInFeature(appView, constClass.getValue())) {
         continue;
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 54b7f8d..d4adf44 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -4,18 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClass.FieldSetter;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,22 +17,15 @@
 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.graph.DexTypeList;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -54,17 +41,14 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Opcodes;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
-import com.android.tools.r8.ir.conversion.PostOptimization;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
@@ -74,24 +58,16 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-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.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -99,16 +75,15 @@
 import java.util.concurrent.ExecutorService;
 import java.util.function.Predicate;
 
-public class EnumUnboxer implements PostOptimization {
+public class EnumUnboxer {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
   // enum if the optimization eventually decides to unbox it.
-  private final Map<DexType, ProgramMethodSet> enumsUnboxingCandidates;
-  private final Map<DexType, Set<DexField>> requiredEnumInstanceFieldData =
-      new ConcurrentHashMap<>();
-  private final Set<DexProgramClass> enumsToUnboxWithPackageRequirement = Sets.newIdentityHashSet();
+  private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
+  private final ProgramPackageCollection enumsToUnboxWithPackageRequirement =
+      ProgramPackageCollection.createEmpty();
 
   private EnumUnboxingRewriter enumUnboxerRewriter;
 
@@ -126,19 +101,13 @@
       debugLogs = null;
     }
     assert !appView.options().debug;
-    enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
-  }
-
-  private void requiredEnumInstanceFieldData(DexType enumType, DexField field) {
-    requiredEnumInstanceFieldData
-        .computeIfAbsent(enumType, ignored -> Sets.newConcurrentHashSet())
-        .add(field);
+    enumUnboxingCandidatesInfo = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
   }
 
   private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
     assert enumClass.isEnum();
     reportFailure(enumClass.type, reason);
-    enumsUnboxingCandidates.remove(enumClass.type);
+    enumUnboxingCandidatesInfo.removeCandidate(enumClass.type);
   }
 
   private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
@@ -156,10 +125,7 @@
   }
 
   private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) {
-    if (!enumsUnboxingCandidates.containsKey(type)) {
-      return null;
-    }
-    return appView.definitionForProgramType(type);
+    return enumUnboxingCandidatesInfo.getCandidateClassOrNull(type);
   }
 
   public void analyzeEnums(IRCode code) {
@@ -222,12 +188,7 @@
     }
     if (!eligibleEnums.isEmpty()) {
       for (DexType eligibleEnum : eligibleEnums) {
-        ProgramMethodSet dependencies = enumsUnboxingCandidates.get(eligibleEnum);
-        // If dependencies is null, it means the enum is not eligible (It has been marked as
-        // unboxable by this thread or another one), so we do not need to record dependencies.
-        if (dependencies != null) {
-          dependencies.add(code.context());
-        }
+        enumUnboxingCandidatesInfo.addMethodDependency(eligibleEnum, code.context());
       }
     }
   }
@@ -289,7 +250,7 @@
     // - as a receiver for a name method, to allow unboxing of:
     //    MyEnum.class.getName();
     DexType enumType = constClass.getValue();
-    if (!enumsUnboxingCandidates.containsKey(enumType)) {
+    if (!enumUnboxingCandidatesInfo.isCandidate(enumType)) {
       return;
     }
     if (constClass.outValue() == null) {
@@ -314,10 +275,15 @@
     }
     // The name data is required for the correct mapping from the enum name to the ordinal in the
     // valueOf utility method.
-    requiredEnumInstanceFieldData(enumType, factory.enumMembers.nameField);
+    addRequiredNameData(enumType);
     eligibleEnums.add(enumType);
   }
 
+  private void addRequiredNameData(DexType enumType) {
+    enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(
+        enumType, factory.enumMembers.nameField);
+  }
+
   private boolean isUnboxableNameMethod(DexMethod method) {
     return method == factory.classMethods.getName
         || method == factory.classMethods.getCanonicalName
@@ -330,7 +296,7 @@
         InvokeMethod invokeMethod = use.asInvokeMethod();
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         for (DexType paramType : invokedMethod.proto.parameters.values) {
-          if (enumsUnboxingCandidates.containsKey(paramType)) {
+          if (enumUnboxingCandidatesInfo.isCandidate(paramType)) {
             eligibleEnums.add(paramType);
           }
         }
@@ -342,12 +308,12 @@
         }
       } else if (use.isFieldPut()) {
         DexType type = use.asFieldInstruction().getField().type;
-        if (enumsUnboxingCandidates.containsKey(type)) {
+        if (enumUnboxingCandidatesInfo.isCandidate(type)) {
           eligibleEnums.add(type);
         }
       } else if (use.isReturn()) {
         DexType returnType = code.method().method.proto.returnType;
-        if (enumsUnboxingCandidates.containsKey(returnType)) {
+        if (enumUnboxingCandidatesInfo.isCandidate(returnType)) {
           eligibleEnums.add(returnType);
         }
       }
@@ -379,22 +345,41 @@
       OptimizationFeedbackDelayed feedback)
       throws ExecutionException {
     EnumInstanceFieldDataMap enumInstanceFieldDataMap = finishAnalysis();
-    // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
-    if (enumsUnboxingCandidates.isEmpty()) {
+    // At this point the enum unboxing candidates are no longer candidates, they will all be
+    // unboxed. We extract the now immutable enums to unbox information and clear the candidate
+    // info.
+    if (enumUnboxingCandidatesInfo.isEmpty()) {
       return;
     }
-    ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
+    ImmutableSet<DexType> enumsToUnbox = enumUnboxingCandidatesInfo.candidates();
+    ImmutableSet<DexProgramClass> enumClassesToUnbox =
+        enumUnboxingCandidatesInfo.candidateClasses();
+    ProgramMethodSet dependencies = enumUnboxingCandidatesInfo.allMethodDependencies();
+    enumUnboxingCandidatesInfo.clear();
     // Update keep info on any of the enum methods of the removed classes.
-    updateKeepInfo(enumsToUnbox);
-    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox, enumInstanceFieldDataMap);
+    updateKeepInfo(enumClassesToUnbox);
     DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
-    Map<DexType, DexType> newMethodLocation = synthesizeUnboxedEnumsMethodsLocations(appBuilder);
+    UnboxedEnumMemberRelocator relocator =
+        UnboxedEnumMemberRelocator.builder(appView)
+            .synthesizeEnumUnboxingUtilityClasses(
+                enumClassesToUnbox, enumsToUnboxWithPackageRequirement, appBuilder)
+            .build();
+    enumUnboxerRewriter =
+        new EnumUnboxingRewriter(appView, enumsToUnbox, enumInstanceFieldDataMap, relocator);
     NestedGraphLens enumUnboxingLens =
-        new TreeFixer(enumsToUnbox).fixupTypeReferences(newMethodLocation);
+        new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
+            .fixupTypeReferences();
     appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
     GraphLens previousLens = appView.graphLens();
     appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
-    // Update optimization info.
+    updateOptimizationInfos(executorService, feedback);
+    postBuilder.put(dependencies);
+    postBuilder.rewrittenWithLens(appView, previousLens);
+  }
+
+  private void updateOptimizationInfos(
+      ExecutorService executorService, OptimizationFeedbackDelayed feedback)
+      throws ExecutionException {
     feedback.fixupOptimizationInfos(
         appView,
         executorService,
@@ -426,88 +411,22 @@
             }
           }
         });
-    postBuilder.put(this);
-    postBuilder.rewrittenWithLens(appView, previousLens);
   }
 
-  // Some enums may have methods which require to stay in the current package for accessibility,
-  // in this case we create another class than the enum unboxing utility class to host these
-  // methods.
-  private Map<DexType, DexType> synthesizeUnboxedEnumsMethodsLocations(
-      DirectMappedDexApplication.Builder appBuilder) {
-    if (enumsToUnboxWithPackageRequirement.isEmpty()) {
-      return Collections.emptyMap();
-    }
-    Map<DexType, DexType> newMethodLocationMap = new IdentityHashMap<>();
-    Map<String, DexProgramClass> packageToClassMap = new HashMap<>();
-    for (DexProgramClass toUnbox : enumsToUnboxWithPackageRequirement) {
-      String packageDescriptor = toUnbox.getType().getPackageDescriptor();
-      DexProgramClass syntheticClass = packageToClassMap.get(packageDescriptor);
-      if (syntheticClass == null) {
-        syntheticClass = synthesizeUtilityClassInPackage(packageDescriptor, appBuilder);
-        packageToClassMap.put(packageDescriptor, syntheticClass);
-      }
-      if (appView.appInfo().getMainDexClasses().contains(toUnbox)) {
-        appView.appInfo().getMainDexClasses().add(syntheticClass);
-      }
-      newMethodLocationMap.put(toUnbox.getType(), syntheticClass.getType());
-    }
-    enumsToUnboxWithPackageRequirement.clear();
-    return newMethodLocationMap;
-  }
-
-  private DexProgramClass synthesizeUtilityClassInPackage(
-      String packageDescriptor, DirectMappedDexApplication.Builder appBuilder) {
-    DexType type =
-        factory.createType(
-            "L"
-                + packageDescriptor
-                + "/"
-                + EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME
-                + ";");
-    if (type == factory.enumUnboxingUtilityType) {
-      return appView.definitionFor(type).asProgramClass();
-    }
-    DexProgramClass syntheticClass =
-        new DexProgramClass(
-            type,
-            null,
-            new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
-            ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-            factory.objectType,
-            DexTypeList.empty(),
-            null,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedMethod.EMPTY_ARRAY,
-            DexEncodedMethod.EMPTY_ARRAY,
-            factory.getSkipNameValidationForTesting(),
-            DexProgramClass::checksumFromType);
-    appBuilder.addSynthesizedClass(syntheticClass);
-    appView.appInfo().addSynthesizedClass(syntheticClass, false);
-    return syntheticClass;
-  }
-
-  private void updateKeepInfo(Set<DexType> enumsToUnbox) {
+  private void updateKeepInfo(Set<DexProgramClass> enumsToUnbox) {
     appView
         .appInfo()
         .getKeepInfo()
         .mutate(
             keepInfo -> {
-              for (DexType type : enumsToUnbox) {
-                DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
-                assert !keepInfo.getClassInfo(clazz).isPinned();
-                clazz.forEachProgramMethod(
+              for (DexProgramClass enumToUnbox : enumsToUnbox) {
+                assert !keepInfo.getClassInfo(enumToUnbox).isPinned();
+                enumToUnbox.forEachProgramMethod(
                     method -> {
                       keepInfo.unsafeAllowMinificationOfMethod(method);
                       keepInfo.unsafeUnpinMethod(method);
                     });
-                clazz.forEachProgramField(
+                enumToUnbox.forEachProgramField(
                     field -> {
                       keepInfo.unsafeAllowMinificationOfField(field);
                       keepInfo.unsafeUnpinField(field);
@@ -529,23 +448,19 @@
   private EnumInstanceFieldDataMap analyzeFields() {
     ImmutableMap.Builder<DexType, ImmutableMap<DexField, EnumInstanceFieldKnownData>> builder =
         ImmutableMap.builder();
-    requiredEnumInstanceFieldData.forEach(
-        (enumType, fields) -> {
+    enumUnboxingCandidatesInfo.forEachCandidateAndRequiredInstanceFieldData(
+        (enumClass, fields) -> {
           ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> typeBuilder =
               ImmutableMap.builder();
-          if (enumsUnboxingCandidates.containsKey(enumType)) {
-            DexProgramClass enumClass = appView.definitionFor(enumType).asProgramClass();
-            assert enumClass != null;
-            for (DexField field : fields) {
-              EnumInstanceFieldData enumInstanceFieldData = computeEnumFieldData(field, enumClass);
-              if (enumInstanceFieldData.isUnknown()) {
-                markEnumAsUnboxable(Reason.MISSING_INSTANCE_FIELD_DATA, enumClass);
-                return;
-              }
-              typeBuilder.put(field, enumInstanceFieldData.asEnumFieldKnownData());
+          for (DexField field : fields) {
+            EnumInstanceFieldData enumInstanceFieldData = computeEnumFieldData(field, enumClass);
+            if (enumInstanceFieldData.isUnknown()) {
+              markEnumAsUnboxable(Reason.MISSING_INSTANCE_FIELD_DATA, enumClass);
+              return;
             }
-            builder.put(enumType, typeBuilder.build());
+            typeBuilder.put(field, enumInstanceFieldData.asEnumFieldKnownData());
           }
+          builder.put(enumClass.type, typeBuilder.build());
         });
     return new EnumInstanceFieldDataMap(builder.build());
   }
@@ -553,15 +468,15 @@
   private void analyzeAccessibility() {
     // Unboxing an enum will require to move its methods to a different class, which may impact
     // accessibility. For a quick analysis we simply reuse the inliner analysis.
-    for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
-      DexProgramClass enumClass = appView.definitionFor(toUnbox).asProgramClass();
-      Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
-      if (classConstraint == Constraint.NEVER) {
-        markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
-      } else if (classConstraint == Constraint.PACKAGE) {
-        enumsToUnboxWithPackageRequirement.add(enumClass);
-      }
-    }
+    enumUnboxingCandidatesInfo.forEachCandidate(
+        enumClass -> {
+          Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
+          if (classConstraint == Constraint.NEVER) {
+            markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
+          } else if (classConstraint == Constraint.PACKAGE) {
+            enumsToUnboxWithPackageRequirement.addProgramClass(enumClass);
+          }
+        });
   }
 
   private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
@@ -784,35 +699,33 @@
   }
 
   private void analyzeInitializers() {
-    for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
-      DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
-      assert enumClass != null;
-
-      boolean hasInstanceInitializer = false;
-      for (DexEncodedMethod directMethod : enumClass.directMethods()) {
-        if (directMethod.isInstanceInitializer()) {
-          hasInstanceInitializer = true;
-          if (directMethod
-              .getOptimizationInfo()
-              .getInstanceInitializerInfo()
-              .mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
-            markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
-            break;
+    enumUnboxingCandidatesInfo.forEachCandidate(
+        enumClass -> {
+          boolean hasInstanceInitializer = false;
+          for (DexEncodedMethod directMethod : enumClass.directMethods()) {
+            if (directMethod.isInstanceInitializer()) {
+              hasInstanceInitializer = true;
+              if (directMethod
+                  .getOptimizationInfo()
+                  .getInstanceInitializerInfo()
+                  .mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+                markEnumAsUnboxable(Reason.INVALID_INIT, enumClass);
+                break;
+              }
+            }
           }
-        }
-      }
-      if (!hasInstanceInitializer) {
-        // This case typically happens when a programmer uses EnumSet/EnumMap without using the
-        // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
-        // We bail out.
-        markEnumAsUnboxable(Reason.NO_INIT, enumClass);
-        continue;
-      }
+          if (!hasInstanceInitializer) {
+            // This case typically happens when a programmer uses EnumSet/EnumMap without using the
+            // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
+            // We bail out.
+            markEnumAsUnboxable(Reason.NO_INIT, enumClass);
+            return;
+          }
 
-      if (enumClass.classInitializationMayHaveSideEffects(appView)) {
-        markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
-      }
-    }
+          if (enumClass.classInitializationMayHaveSideEffects(appView)) {
+            markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
+          }
+        });
   }
 
   private Reason instructionAllowEnumUnboxing(
@@ -834,7 +747,7 @@
         return Reason.INVALID_INVOKE;
       }
       DexMethod singleTarget = encodedSingleTarget.method;
-      DexClass dexClass = appView.definitionFor(singleTarget.holder);
+      DexClass dexClass = appView.definitionFor(singleTarget.holder, code.context());
       if (dexClass == null) {
         assert false;
         return Reason.INVALID_INVOKE;
@@ -878,7 +791,7 @@
           return Reason.ELIGIBLE;
         }
         if (singleTarget == factory.stringMembers.valueOf) {
-          requiredEnumInstanceFieldData(enumClass.type, factory.enumMembers.nameField);
+          addRequiredNameData(enumClass.type);
           return Reason.ELIGIBLE;
         }
         if (singleTarget == factory.objectMembers.getClass
@@ -900,14 +813,14 @@
       } else if (singleTarget == factory.enumMembers.nameMethod
           || singleTarget == factory.enumMembers.toString) {
         assert invokeMethod.asInvokeMethodWithReceiver().getReceiver() == enumValue;
-        requiredEnumInstanceFieldData(enumClass.type, factory.enumMembers.nameField);
+        addRequiredNameData(enumClass.type);
         return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMembers.ordinalMethod) {
         return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMembers.hashCode) {
         return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMembers.constructor) {
-        // Enum constructor call is allowed only if first call of an enum initializer.
+        // Enum constructor call is allowed only if called from an enum initializer.
         if (code.method().isInstanceInitializer() && code.method().holder() == enumClass.type) {
           return Reason.ELIGIBLE;
         }
@@ -924,7 +837,7 @@
       if (field == null) {
         return Reason.INVALID_FIELD_PUT;
       }
-      DexProgramClass dexClass = appView.definitionForProgramType(field.holder());
+      DexProgramClass dexClass = appView.programDefinitionFor(field.holder(), code.context());
       if (dexClass == null) {
         return Reason.INVALID_FIELD_PUT;
       }
@@ -943,7 +856,7 @@
       InstanceGet instanceGet = instruction.asInstanceGet();
       assert instanceGet.getField().holder == enumClass.type;
       DexField field = instanceGet.getField();
-      requiredEnumInstanceFieldData(enumClass.type, field);
+      enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass.type, field);
       return Reason.ELIGIBLE;
     }
 
@@ -1112,12 +1025,13 @@
   private void reportEnumsAnalysis() {
     assert debugLogEnabled;
     Reporter reporter = appView.options().reporter;
+    Set<DexType> candidates = enumUnboxingCandidatesInfo.candidates();
     reporter.info(
         new StringDiagnostic(
             "Unboxed enums (Unboxing succeeded "
-                + enumsUnboxingCandidates.size()
+                + candidates.size()
                 + "): "
-                + Arrays.toString(enumsUnboxingCandidates.keySet().toArray())));
+                + Arrays.toString(candidates.toArray())));
     StringBuilder sb = new StringBuilder();
     sb.append("Boxed enums (Unboxing failed ").append(debugLogs.size()).append("):\n");
     for (DexType enumType : debugLogs.keySet()) {
@@ -1145,29 +1059,13 @@
     return Sets.newIdentityHashSet();
   }
 
-  public void synthesizeUtilityMethods(
-      Builder<?> builder, IRConverter converter, ExecutorService executorService)
+  public void synthesizeUtilityMethods(IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     if (enumUnboxerRewriter != null) {
-      enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(builder, converter, executorService);
+      enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(converter, executorService);
     }
   }
 
-  @Override
-  public ProgramMethodSet methodsToRevisit() {
-    ProgramMethodSet toReprocess = ProgramMethodSet.create();
-    for (ProgramMethodSet methods : enumsUnboxingCandidates.values()) {
-      toReprocess.addAll(methods);
-    }
-    return toReprocess;
-  }
-
-  @Override
-  public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
-    // Answers null so default optimization setup is performed.
-    return null;
-  }
-
   public enum Reason {
     ELIGIBLE,
     ACCESSIBILITY,
@@ -1205,307 +1103,4 @@
     ENUM_METHOD_CALLED_WITH_NULL_RECEIVER,
     OTHER_UNSUPPORTED_INSTRUCTION;
   }
-
-  private class TreeFixer {
-
-    private final Map<DexType, List<DexEncodedMethod>> unboxedEnumsMethods =
-        new IdentityHashMap<>();
-    private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
-    private final Set<DexType> enumsToUnbox;
-
-    private TreeFixer(Set<DexType> enumsToUnbox) {
-      this.enumsToUnbox = enumsToUnbox;
-    }
-
-    private NestedGraphLens fixupTypeReferences(Map<DexType, DexType> newMethodLocation) {
-      assert enumUnboxerRewriter != null;
-      // Fix all methods and fields using enums to unbox.
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
-        if (enumsToUnbox.contains(clazz.type)) {
-          // Clear the initializers and move the static methods to the new location.
-          Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
-          clazz
-              .methods()
-              .forEach(
-                  m -> {
-                    if (m.isInitializer()) {
-                      clearEnumToUnboxMethod(m);
-                    } else {
-                      DexType newHolder =
-                          newMethodLocation.getOrDefault(
-                              clazz.type, factory.enumUnboxingUtilityType);
-                      List<DexEncodedMethod> movedMethods =
-                          unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
-                      movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
-                      methodsToRemove.add(m);
-                    }
-                  });
-          clazz.getMethodCollection().removeMethods(methodsToRemove);
-        } else {
-          clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
-          fixupFields(clazz.staticFields(), clazz::setStaticField);
-          fixupFields(clazz.instanceFields(), clazz::setInstanceField);
-        }
-      }
-      for (DexType toUnbox : enumsToUnbox) {
-        lensBuilder.map(toUnbox, factory.intType);
-      }
-      DexProgramClass utilityClass =
-          appView.definitionForProgramType(factory.enumUnboxingUtilityType);
-      assert utilityClass != null : "Should have been synthesized upfront";
-      unboxedEnumsMethods.forEach(
-          (newHolderType, movedMethods) -> {
-            DexProgramClass newHolderClass = appView.definitionFor(newHolderType).asProgramClass();
-            newHolderClass.addDirectMethods(movedMethods);
-          });
-      return lensBuilder.build(factory, appView.graphLens(), enumsToUnbox);
-    }
-
-    private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) {
-      // The compiler may have references to the enum methods, but such methods will be removed
-      // and they cannot be reprocessed since their rewriting through the lensCodeRewriter/
-      // enumUnboxerRewriter will generate invalid code.
-      // To work around this problem we clear such methods, i.e., we replace the code object by
-      // an empty throwing code object, so reprocessing won't take time and will be valid.
-      enumMethod.setCode(enumMethod.buildEmptyThrowingCode(appView.options()), appView);
-    }
-
-    private DexEncodedMethod fixupEncodedMethodToUtility(
-        DexEncodedMethod encodedMethod, DexType newHolder) {
-      DexMethod method = encodedMethod.method;
-      DexString newMethodName =
-          factory.createString(
-              enumUnboxerRewriter.compatibleName(method.holder)
-                  + "$"
-                  + (encodedMethod.isStatic() ? "s" : "v")
-                  + "$"
-                  + method.name.toString());
-      DexProto proto =
-          encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
-      DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
-      assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
-      lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
-      encodedMethod.accessFlags.promoteToPublic();
-      encodedMethod.accessFlags.promoteToStatic();
-      encodedMethod.clearAnnotations();
-      encodedMethod.clearParameterAnnotations();
-      return encodedMethod.toTypeSubstitutedMethod(newMethod);
-    }
-
-    private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
-      DexProto newProto = fixupProto(encodedMethod.proto());
-      if (newProto == encodedMethod.proto()) {
-        return encodedMethod;
-      }
-      assert !encodedMethod.isClassInitializer();
-      // We add the $enumunboxing$ suffix to make sure we do not create a library override.
-      String newMethodName =
-          encodedMethod.getName().toString()
-              + (encodedMethod.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
-      DexMethod newMethod = factory.createMethod(encodedMethod.holder(), newProto, newMethodName);
-      newMethod = ensureUniqueMethod(encodedMethod, newMethod);
-      int numberOfExtraNullParameters = newMethod.getArity() - encodedMethod.method.getArity();
-      boolean isStatic = encodedMethod.isStatic();
-      lensBuilder.move(
-          encodedMethod.method, isStatic, newMethod, isStatic, numberOfExtraNullParameters);
-      DexEncodedMethod newEncodedMethod = encodedMethod.toTypeSubstitutedMethod(newMethod);
-      assert !encodedMethod.isLibraryMethodOverride().isTrue()
-          : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
-      if (newEncodedMethod.isNonPrivateVirtualMethod()) {
-        newEncodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
-      }
-      return newEncodedMethod;
-    }
-
-    private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
-      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, factory.enumUnboxingUtilityType),
-                  newMethod.name);
-        }
-      } else {
-        int index = 0;
-        while (holder.lookupMethod(newMethod) != null) {
-          newMethod =
-              factory.createMethod(
-                  newMethod.holder,
-                  newMethod.proto,
-                  encodedMethod.getName().toString() + "$enumunboxing$" + index++);
-        }
-      }
-      return newMethod;
-    }
-
-    private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
-      if (fields == null) {
-        return;
-      }
-      for (int i = 0; i < fields.size(); i++) {
-        DexEncodedField encodedField = fields.get(i);
-        DexField field = encodedField.field;
-        DexType newType = fixupType(field.type);
-        if (newType != field.type) {
-          DexField newField = factory.createField(field.holder, newType, field.name);
-          lensBuilder.move(field, newField);
-          DexEncodedField newEncodedField = encodedField.toTypeSubstitutedField(newField);
-          setter.setField(i, newEncodedField);
-          if (encodedField.isStatic() && encodedField.hasExplicitStaticValue()) {
-            assert encodedField.getStaticValue() == DexValueNull.NULL;
-            newEncodedField.setStaticValue(DexValueInt.DEFAULT);
-            // TODO(b/150593449): Support conversion from DexValueEnum to DexValueInt.
-          }
-        }
-      }
-    }
-
-    private DexProto fixupProto(DexProto proto) {
-      DexType returnType = fixupType(proto.returnType);
-      DexType[] arguments = fixupTypes(proto.parameters.values);
-      return factory.createProto(returnType, arguments);
-    }
-
-    private DexType fixupType(DexType type) {
-      if (type.isArrayType()) {
-        DexType base = type.toBaseType(factory);
-        DexType fixed = fixupType(base);
-        if (base == fixed) {
-          return type;
-        }
-        return type.replaceBaseType(fixed, factory);
-      }
-      if (type.isClassType() && enumsToUnbox.contains(type)) {
-        DexType intType = factory.intType;
-        lensBuilder.map(type, intType);
-        return intType;
-      }
-      return type;
-    }
-
-    private DexType[] fixupTypes(DexType[] types) {
-      DexType[] result = new DexType[types.length];
-      for (int i = 0; i < result.length; i++) {
-        result[i] = fixupType(types[i]);
-      }
-      return result;
-    }
-  }
-
-  private static class EnumUnboxingLens extends NestedGraphLens {
-
-    private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
-    private final Set<DexType> unboxedEnums;
-
-    EnumUnboxingLens(
-        Map<DexType, DexType> typeMap,
-        Map<DexMethod, DexMethod> methodMap,
-        Map<DexField, DexField> fieldMap,
-        BiMap<DexField, DexField> originalFieldSignatures,
-        BiMap<DexMethod, DexMethod> originalMethodSignatures,
-        GraphLens previousLens,
-        DexItemFactory dexItemFactory,
-        Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
-        Set<DexType> unboxedEnums) {
-      super(
-          typeMap,
-          methodMap,
-          fieldMap,
-          originalFieldSignatures,
-          originalMethodSignatures,
-          previousLens,
-          dexItemFactory);
-      this.prototypeChangesPerMethod = prototypeChangesPerMethod;
-      this.unboxedEnums = unboxedEnums;
-    }
-
-    @Override
-    protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-        RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-      // During the second IR processing enum unboxing is the only optimization rewriting
-      // prototype description, if this does not hold, remove the assertion and merge
-      // the two prototype changes.
-      assert prototypeChanges.isEmpty();
-      return prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none());
-    }
-
-    @Override
-    protected Invoke.Type mapInvocationType(
-        DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
-      if (unboxedEnums.contains(originalMethod.holder)) {
-        // Methods moved from unboxed enums to the utility class are either static or statified.
-        assert newMethod != originalMethod;
-        return Invoke.Type.STATIC;
-      }
-      return type;
-    }
-
-    public static Builder builder() {
-      return new Builder();
-    }
-
-    private static class Builder extends NestedGraphLens.Builder {
-
-      private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
-          new IdentityHashMap<>();
-
-      public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
-        move(from, fromStatic, to, toStatic, 0);
-      }
-
-      public void move(
-          DexMethod from,
-          boolean fromStatic,
-          DexMethod to,
-          boolean toStatic,
-          int numberOfExtraNullParameters) {
-        super.move(from, to);
-        int offsetDiff = 0;
-        int toOffset = BooleanUtils.intValue(!toStatic);
-        ArgumentInfoCollection.Builder builder = ArgumentInfoCollection.builder();
-        if (fromStatic != toStatic) {
-          assert toStatic;
-          offsetDiff = 1;
-          builder.addArgumentInfo(
-              0, new RewrittenTypeInfo(from.holder, to.proto.parameters.values[0]));
-        }
-        for (int i = 0; i < from.proto.parameters.size(); i++) {
-          DexType fromType = from.proto.parameters.values[i];
-          DexType toType = to.proto.parameters.values[i + offsetDiff];
-          if (fromType != toType) {
-            builder.addArgumentInfo(
-                i + offsetDiff + toOffset, new RewrittenTypeInfo(fromType, toType));
-          }
-        }
-        RewrittenTypeInfo returnInfo =
-            from.proto.returnType == to.proto.returnType
-                ? null
-                : new RewrittenTypeInfo(from.proto.returnType, to.proto.returnType);
-        prototypeChangesPerMethod.put(
-            to,
-            RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build())
-                .withExtraUnusedNullParameters(numberOfExtraNullParameters));
-      }
-
-      public EnumUnboxingLens build(
-          DexItemFactory dexItemFactory, GraphLens previousLens, Set<DexType> unboxedEnums) {
-        if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
-          return null;
-        }
-        return new EnumUnboxingLens(
-            typeMap,
-            methodMap,
-            fieldMap,
-            originalFieldSignatures,
-            originalMethodSignatures,
-            previousLens,
-            dexItemFactory,
-            ImmutableMap.copyOf(prototypeChangesPerMethod),
-            ImmutableSet.copyOf(unboxedEnums));
-      }
-    }
-  }
 }
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 b5a5b62..e0b6ff5 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
@@ -16,9 +16,6 @@
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 
 class EnumUnboxingCandidateAnalysis {
 
@@ -30,7 +27,8 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final EnumUnboxer enumUnboxer;
   private final DexItemFactory factory;
-  private Map<DexType, ProgramMethodSet> enumToUnboxCandidates = new ConcurrentHashMap<>();
+  private EnumUnboxingCandidateInfoCollection enumToUnboxCandidates =
+      new EnumUnboxingCandidateInfoCollection();
 
   EnumUnboxingCandidateAnalysis(AppView<AppInfoWithLiveness> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
@@ -38,16 +36,16 @@
     factory = appView.dexItemFactory();
   }
 
-  Map<DexType, ProgramMethodSet> findCandidates() {
+  EnumUnboxingCandidateInfoCollection findCandidates() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (isEnumUnboxingCandidate(clazz)) {
-        enumToUnboxCandidates.put(clazz.type, ProgramMethodSet.createConcurrent());
+        enumToUnboxCandidates.addCandidate(clazz);
       }
     }
     removeEnumsInAnnotations();
     removePinnedCandidates();
     if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
-      enumToUnboxCandidates.remove(appView.protoShrinker().references.methodToInvokeType);
+      enumToUnboxCandidates.removeCandidate(appView.protoShrinker().references.methodToInvokeType);
     }
     return enumToUnboxCandidates;
   }
@@ -114,9 +112,9 @@
       assert method.parameters().isEmpty()
           || appView.options().testing.allowInjectedAnnotationMethods;
       DexType valueType = method.returnType().toBaseType(appView.dexItemFactory());
-      if (enumToUnboxCandidates.containsKey(valueType)) {
+      if (enumToUnboxCandidates.isCandidate(valueType)) {
         enumUnboxer.reportFailure(valueType, Reason.ANNOTATION);
-        enumToUnboxCandidates.remove(valueType);
+        enumToUnboxCandidates.removeCandidate(valueType);
       }
     }
   }
@@ -146,9 +144,9 @@
   }
 
   private void removePinnedCandidate(DexType type) {
-    if (enumToUnboxCandidates.containsKey(type)) {
+    if (enumToUnboxCandidates.isCandidate(type)) {
       enumUnboxer.reportFailure(type, Reason.PINNED);
-      enumToUnboxCandidates.remove(type);
+      enumToUnboxCandidates.removeCandidate(type);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
new file mode 100644
index 0000000..947cc6d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.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.ir.optimize.enums;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class EnumUnboxingCandidateInfoCollection {
+
+  private final Map<DexType, EnumUnboxingCandidateInfo> enumTypeToInfo = new ConcurrentHashMap<>();
+
+  public void addCandidate(DexProgramClass enumClass) {
+    assert !enumTypeToInfo.containsKey(enumClass.type);
+    enumTypeToInfo.put(enumClass.type, new EnumUnboxingCandidateInfo(enumClass));
+  }
+
+  public void removeCandidate(DexType enumType) {
+    enumTypeToInfo.remove(enumType);
+  }
+
+  public boolean isCandidate(DexType enumType) {
+    return enumTypeToInfo.containsKey(enumType);
+  }
+
+  public boolean isEmpty() {
+    return enumTypeToInfo.isEmpty();
+  }
+
+  public ImmutableSet<DexType> candidates() {
+    return ImmutableSet.copyOf(enumTypeToInfo.keySet());
+  }
+
+  public ImmutableSet<DexProgramClass> candidateClasses() {
+    ImmutableSet.Builder<DexProgramClass> builder = ImmutableSet.builder();
+    for (EnumUnboxingCandidateInfo info : enumTypeToInfo.values()) {
+      builder.add(info.getEnumClass());
+    }
+    return builder.build();
+  }
+
+  public DexProgramClass getCandidateClassOrNull(DexType enumType) {
+    EnumUnboxingCandidateInfo info = enumTypeToInfo.get(enumType);
+    if (info == null) {
+      return null;
+    }
+    return info.enumClass;
+  }
+
+  public ProgramMethodSet allMethodDependencies() {
+    ProgramMethodSet allMethodDependencies = ProgramMethodSet.create();
+    for (EnumUnboxingCandidateInfo info : enumTypeToInfo.values()) {
+      allMethodDependencies.addAll(info.methodDependencies);
+    }
+    return allMethodDependencies;
+  }
+
+  public void addMethodDependency(DexType enumType, ProgramMethod programMethod) {
+    // The enumType may be removed concurrently map from enumTypeToInfo. It means in that
+    // case the enum is no longer a candidate, and dependencies don't need to be recorded
+    // anymore.
+    EnumUnboxingCandidateInfo info = enumTypeToInfo.get(enumType);
+    if (info == null) {
+      return;
+    }
+    info.addMethodDependency(programMethod);
+  }
+
+  public void addRequiredEnumInstanceFieldData(DexType enumType, DexField field) {
+    // The enumType may be removed concurrently map from enumTypeToInfo. It means in that
+    // case the enum is no longer a candidate, and dependencies don't need to be recorded
+    // anymore.
+    EnumUnboxingCandidateInfo info = enumTypeToInfo.get(enumType);
+    if (info == null) {
+      return;
+    }
+    info.addRequiredInstanceFieldData(field);
+  }
+
+  public void forEachCandidate(Consumer<DexProgramClass> enumClassConsumer) {
+    enumTypeToInfo.values().forEach(info -> enumClassConsumer.accept(info.enumClass));
+  }
+
+  public void forEachCandidateAndRequiredInstanceFieldData(
+      BiConsumer<DexProgramClass, Set<DexField>> biConsumer) {
+    enumTypeToInfo
+        .values()
+        .forEach(
+            info -> biConsumer.accept(info.getEnumClass(), info.getRequiredInstanceFieldData()));
+  }
+
+  public void clear() {
+    enumTypeToInfo.clear();
+  }
+
+  private static class EnumUnboxingCandidateInfo {
+
+    private final DexProgramClass enumClass;
+    private final ProgramMethodSet methodDependencies = ProgramMethodSet.createConcurrent();
+    private final Set<DexField> requiredInstanceFieldData = Sets.newConcurrentHashSet();
+
+    public EnumUnboxingCandidateInfo(DexProgramClass enumClass) {
+      assert enumClass != null;
+      this.enumClass = enumClass;
+    }
+
+    public DexProgramClass getEnumClass() {
+      return enumClass;
+    }
+
+    public void addMethodDependency(ProgramMethod programMethod) {
+      methodDependencies.add(programMethod);
+    }
+
+    public void addRequiredInstanceFieldData(DexField field) {
+      requiredInstanceFieldData.add(field);
+    }
+
+    public Set<DexField> getRequiredInstanceFieldData() {
+      return requiredInstanceFieldData;
+    }
+  }
+}
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
new file mode 100644
index 0000000..869cb6e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -0,0 +1,139 @@
+// 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.optimize.enums;
+
+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.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+class EnumUnboxingLens extends GraphLens.NestedGraphLens {
+
+  private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
+  private final Set<DexType> unboxedEnums;
+
+  EnumUnboxingLens(
+      Map<DexType, DexType> typeMap,
+      Map<DexMethod, DexMethod> methodMap,
+      Map<DexField, DexField> fieldMap,
+      BiMap<DexField, DexField> originalFieldSignatures,
+      BiMap<DexMethod, DexMethod> originalMethodSignatures,
+      GraphLens previousLens,
+      DexItemFactory dexItemFactory,
+      Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
+      Set<DexType> unboxedEnums) {
+    super(
+        typeMap,
+        methodMap,
+        fieldMap,
+        originalFieldSignatures,
+        originalMethodSignatures,
+        previousLens,
+        dexItemFactory);
+    this.prototypeChangesPerMethod = prototypeChangesPerMethod;
+    this.unboxedEnums = unboxedEnums;
+  }
+
+  @Override
+  protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+    // During the second IR processing enum unboxing is the only optimization rewriting
+    // prototype description, if this does not hold, remove the assertion and merge
+    // the two prototype changes.
+    assert prototypeChanges.isEmpty();
+    return prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none());
+  }
+
+  @Override
+  protected Invoke.Type mapInvocationType(
+      DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
+    if (unboxedEnums.contains(originalMethod.holder)) {
+      // Methods moved from unboxed enums to the utility class are either static or statified.
+      assert newMethod != originalMethod;
+      return Invoke.Type.STATIC;
+    }
+    return type;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  static class Builder extends NestedGraphLens.Builder {
+
+    private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
+        new IdentityHashMap<>();
+
+    public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
+      move(from, fromStatic, to, toStatic, 0);
+    }
+
+    public void move(
+        DexMethod from,
+        boolean fromStatic,
+        DexMethod to,
+        boolean toStatic,
+        int numberOfExtraNullParameters) {
+      super.move(from, to);
+      int offsetDiff = 0;
+      int toOffset = BooleanUtils.intValue(!toStatic);
+      RewrittenPrototypeDescription.ArgumentInfoCollection.Builder builder =
+          RewrittenPrototypeDescription.ArgumentInfoCollection.builder();
+      if (fromStatic != toStatic) {
+        assert toStatic;
+        offsetDiff = 1;
+        builder.addArgumentInfo(
+            0,
+            new RewrittenPrototypeDescription.RewrittenTypeInfo(
+                from.holder, to.proto.parameters.values[0]));
+      }
+      for (int i = 0; i < from.proto.parameters.size(); i++) {
+        DexType fromType = from.proto.parameters.values[i];
+        DexType toType = to.proto.parameters.values[i + offsetDiff];
+        if (fromType != toType) {
+          builder.addArgumentInfo(
+              i + offsetDiff + toOffset,
+              new RewrittenPrototypeDescription.RewrittenTypeInfo(fromType, toType));
+        }
+      }
+      RewrittenPrototypeDescription.RewrittenTypeInfo returnInfo =
+          from.proto.returnType == to.proto.returnType
+              ? null
+              : new RewrittenPrototypeDescription.RewrittenTypeInfo(
+                  from.proto.returnType, to.proto.returnType);
+      prototypeChangesPerMethod.put(
+          to,
+          RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build())
+              .withExtraUnusedNullParameters(numberOfExtraNullParameters));
+    }
+
+    public EnumUnboxingLens build(
+        DexItemFactory dexItemFactory, GraphLens previousLens, Set<DexType> unboxedEnums) {
+      if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
+        return null;
+      }
+      return new EnumUnboxingLens(
+          typeMap,
+          methodMap,
+          fieldMap,
+          originalFieldSignatures,
+          originalMethodSignatures,
+          previousLens,
+          dexItemFactory,
+          ImmutableMap.copyOf(prototypeChangesPerMethod),
+          ImmutableSet.copyOf(unboxedEnums));
+    }
+  }
+}
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 6c73aef..c67c231 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
@@ -9,23 +9,23 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
 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.DexMember;
 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.graph.DexTypeList;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -46,11 +46,12 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
-import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -63,7 +64,6 @@
 
 public class EnumUnboxingRewriter {
 
-  public static final String ENUM_UNBOXING_UTILITY_CLASS_NAME = "$r8$EnumUnboxingUtility";
   public static final String ENUM_UNBOXING_UTILITY_METHOD_PREFIX = "$enumboxing$";
   private static final int REQUIRED_CLASS_FILE_VERSION = 52;
 
@@ -71,8 +71,10 @@
   private final DexItemFactory factory;
   private final EnumValueInfoMapCollection enumsToUnbox;
   private final EnumInstanceFieldDataMap unboxedEnumsInstanceFieldData;
+  private final UnboxedEnumMemberRelocator relocator;
+
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
-  private final Map<DexField, DexEncodedField> extraUtilityFields = new ConcurrentHashMap<>();
+  private final Map<DexField, DexEncodedField> utilityFields = new ConcurrentHashMap<>();
 
   private final DexMethod ordinalUtilityMethod;
   private final DexMethod equalsUtilityMethod;
@@ -84,7 +86,8 @@
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
       Set<DexType> enumsToUnbox,
-      EnumInstanceFieldDataMap unboxedEnumsInstanceFieldData) {
+      EnumInstanceFieldDataMap unboxedEnumsInstanceFieldData,
+      UnboxedEnumMemberRelocator relocator) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     EnumValueInfoMapCollection.Builder builder = EnumValueInfoMapCollection.builder();
@@ -94,38 +97,40 @@
     }
     this.enumsToUnbox = builder.build();
     this.unboxedEnumsInstanceFieldData = unboxedEnumsInstanceFieldData;
+    this.relocator = relocator;
 
     // Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
+    DexType defaultEnumUnboxingUtility = relocator.getDefaultEnumUnboxingUtility();
     this.ordinalUtilityMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            defaultEnumUnboxingUtility,
             factory.createProto(factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
     this.equalsUtilityMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            defaultEnumUnboxingUtility,
             factory.createProto(factory.booleanType, factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
     this.compareToUtilityMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            defaultEnumUnboxingUtility,
             factory.createProto(factory.intType, factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
     // Custom methods for generated field $VALUES initialization.
     this.valuesUtilityMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            defaultEnumUnboxingUtility,
             factory.createProto(factory.intArrayType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "values");
     // Custom methods for Object#getClass without outValue and Objects.requireNonNull.
     this.zeroCheckMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            defaultEnumUnboxingUtility,
             factory.createProto(factory.voidType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
     this.zeroCheckMessageMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            defaultEnumUnboxingUtility,
             factory.createProto(factory.voidType, factory.intType, factory.stringType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
   }
@@ -261,7 +266,7 @@
             utilityMethods.computeIfAbsent(
                 valuesUtilityMethod, m -> synthesizeValuesUtilityMethod());
             DexField fieldValues = createValuesField(holder);
-            extraUtilityFields.computeIfAbsent(fieldValues, this::computeValuesEncodedField);
+            utilityFields.computeIfAbsent(fieldValues, this::computeValuesEncodedField);
             DexMethod methodValues = createValuesMethod(holder);
             utilityMethods.computeIfAbsent(
                 methodValues,
@@ -368,11 +373,11 @@
     return type.toSourceString().replace('.', '$');
   }
 
-  private DexField createValuesField(DexType type) {
+  private DexField createValuesField(DexType enumType) {
     return factory.createField(
-        factory.enumUnboxingUtilityType,
+        relocator.getNewMemberLocationFor(enumType),
         factory.intArrayType,
-        factory.enumValuesFieldName + "$field$" + compatibleName(type));
+        factory.enumValuesFieldName + "$field$" + compatibleName(enumType));
   }
 
   private DexEncodedField computeValuesEncodedField(DexField field) {
@@ -384,22 +389,18 @@
         null);
   }
 
-  private DexMethod createValuesMethod(DexType type) {
+  private DexMethod createValuesMethod(DexType enumType) {
     return factory.createMethod(
-        factory.enumUnboxingUtilityType,
+        relocator.getNewMemberLocationFor(enumType),
         factory.createProto(factory.intArrayType),
-        factory.enumValuesFieldName + "$method$" + compatibleName(type));
+        factory.enumValuesFieldName + "$method$" + compatibleName(enumType));
   }
 
   private DexEncodedMethod computeValuesEncodedMethod(
       DexMethod method, DexField fieldValues, int numEnumInstances) {
     CfCode cfCode =
         new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
-                appView,
-                factory.enumUnboxingUtilityType,
-                fieldValues,
-                numEnumInstances,
-                valuesUtilityMethod)
+                appView, method.holder, fieldValues, numEnumInstances, valuesUtilityMethod)
             .generateCfCode();
     return synthesizeUtilityMethod(cfCode, method, true);
   }
@@ -415,7 +416,7 @@
             + compatibleName(enumType);
     DexMethod fieldMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            relocator.getNewMemberLocationFor(enumType),
             factory.createProto(field.type, factory.intType),
             methodName);
     utilityMethods.computeIfAbsent(
@@ -429,7 +430,7 @@
     String methodName = "string$valueOf$" + compatibleName(enumType);
     DexMethod fieldMethod =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            relocator.getNewMemberLocationFor(enumType),
             factory.createProto(factory.stringType, factory.intType),
             methodName);
     AbstractValue nullString =
@@ -440,14 +441,14 @@
     return fieldMethod;
   }
 
-  private DexMethod computeValueOfUtilityMethod(DexType type) {
-    assert enumsToUnbox.containsEnum(type);
+  private DexMethod computeValueOfUtilityMethod(DexType enumType) {
+    assert enumsToUnbox.containsEnum(enumType);
     DexMethod valueOf =
         factory.createMethod(
-            factory.enumUnboxingUtilityType,
+            relocator.getNewMemberLocationFor(enumType),
             factory.createProto(factory.intType, factory.stringType),
-            "valueOf" + compatibleName(type));
-    utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, type));
+            "valueOf" + compatibleName(enumType));
+    utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
     return valueOf;
   }
 
@@ -468,50 +469,52 @@
     return enumsToUnbox.containsEnum(classType) ? classType : null;
   }
 
-  void synthesizeEnumUnboxingUtilityMethods(
-      Builder<?> builder, IRConverter converter, ExecutorService executorService)
+  void synthesizeEnumUnboxingUtilityMethods(IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
-    // Synthesize a class which holds various utility methods that may be called from the IR
-    // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
-    List<DexEncodedMethod> requiredMethods = new ArrayList<>(utilityMethods.values());
-    // Sort for deterministic order.
-    requiredMethods.sort((m1, m2) -> m1.method.name.slowCompareTo(m2.method.name));
-    if (requiredMethods.isEmpty()) {
+    // Append to the various utility classes, in deterministic order, the utility methods and
+    // fields required.
+    Map<DexType, List<DexEncodedMethod>> methodMap = triageEncodedMembers(utilityMethods.values());
+    if (methodMap.isEmpty()) {
+      assert utilityFields.isEmpty();
       return;
     }
-    List<DexEncodedField> fields = new ArrayList<>(extraUtilityFields.values());
-    fields.sort((f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
-    DexProgramClass utilityClass =
-        appView.definitionForProgramType(factory.enumUnboxingUtilityType);
-    assert utilityClass != null : "Should have been synthesized upfront.";
-    utilityClass.appendStaticFields(fields);
-    utilityClass.addDirectMethods(requiredMethods);
-    assert requiredMethods.stream().allMatch(DexEncodedMethod::isPublic);
-    // TODO(b/147860220): Use processMethodsConcurrently on requiredMethods instead.
-    converter.optimizeSynthesizedClass(utilityClass, executorService);
+    SortedProgramMethodSet wave = SortedProgramMethodSet.create();
+    methodMap.forEach(
+        (type, methodsSorted) -> {
+          DexProgramClass utilityClass = appView.definitionFor(type).asProgramClass();
+          assert utilityClass != null;
+          utilityClass.addDirectMethods(methodsSorted);
+          for (DexEncodedMethod dexEncodedMethod : methodsSorted) {
+            wave.add(new ProgramMethod(utilityClass, dexEncodedMethod));
+          }
+        });
+    Map<DexType, List<DexEncodedField>> fieldMap = triageEncodedMembers(utilityFields.values());
+    fieldMap.forEach(
+        (type, fieldsSorted) -> {
+          DexProgramClass utilityClass = appView.definitionFor(type).asProgramClass();
+          assert utilityClass != null;
+          utilityClass.appendStaticFields(fieldsSorted);
+        });
+    converter.processMethodsConcurrently(wave, executorService);
   }
 
-  public static DexProgramClass synthesizeEmptyEnumUnboxingUtilityClass(AppView<?> appView) {
-    DexItemFactory factory = appView.dexItemFactory();
-    return new DexProgramClass(
-        factory.enumUnboxingUtilityType,
-        null,
-        new SynthesizedOrigin("EnumUnboxing ", EnumUnboxingRewriter.class),
-        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-        factory.objectType,
-        DexTypeList.empty(),
-        factory.createString("enumunboxing"),
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        factory.getSkipNameValidationForTesting(),
-        DexProgramClass::checksumFromType);
+  <R extends DexMember<T, R>, T extends DexEncodedMember<T, R>>
+      Map<DexType, List<T>> triageEncodedMembers(Collection<T> encodedMembers) {
+    if (encodedMembers.isEmpty()) {
+      return Collections.emptyMap();
+    }
+    Map<DexType, List<T>> encodedMembersMap = new IdentityHashMap<>();
+    // We compute encodedMembers by types.
+    for (T encodedMember : encodedMembers) {
+      List<T> members =
+          encodedMembersMap.computeIfAbsent(encodedMember.holder(), ignored -> new ArrayList<>());
+      members.add(encodedMember);
+    }
+    // We make the order deterministic.
+    for (List<T> value : encodedMembersMap.values()) {
+      value.sort((m1, m2) -> m1.toReference().slowCompareTo(m2.toReference()));
+    }
+    return encodedMembersMap;
   }
 
   private DexEncodedMethod synthesizeInstanceFieldMethod(
@@ -521,7 +524,7 @@
     CfCode cfCode =
         new EnumUnboxingCfCodeProvider.EnumUnboxingInstanceFieldCfCodeProvider(
                 appView,
-                factory.enumUnboxingUtilityType,
+                method.holder,
                 field.type,
                 enumsToUnbox.getEnumValueInfoMap(enumType),
                 unboxedEnumsInstanceFieldData
@@ -540,7 +543,7 @@
     CfCode cfCode =
         new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
                 appView,
-                factory.enumUnboxingUtilityType,
+                method.holder,
                 enumType,
                 enumsToUnbox.getEnumValueInfoMap(enumType),
                 unboxedEnumsInstanceFieldData
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
new file mode 100644
index 0000000..879c909
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -0,0 +1,221 @@
+// 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.optimize.enums;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+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.graph.DexValue;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+class EnumUnboxingTreeFixer {
+
+  private final Map<DexType, List<DexEncodedMethod>> unboxedEnumsMethods = new IdentityHashMap<>();
+  private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+  private final Set<DexType> enumsToUnbox;
+  private final UnboxedEnumMemberRelocator relocator;
+  private final EnumUnboxingRewriter enumUnboxerRewriter;
+
+  EnumUnboxingTreeFixer(
+      AppView<?> appView,
+      Set<DexType> enumsToUnbox,
+      UnboxedEnumMemberRelocator relocator,
+      EnumUnboxingRewriter enumUnboxerRewriter) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+    this.enumsToUnbox = enumsToUnbox;
+    this.relocator = relocator;
+    this.enumUnboxerRewriter = enumUnboxerRewriter;
+  }
+
+  GraphLens.NestedGraphLens fixupTypeReferences() {
+    assert enumUnboxerRewriter != null;
+    // Fix all methods and fields using enums to unbox.
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (enumsToUnbox.contains(clazz.type)) {
+        // Clear the initializers and move the static methods to the new location.
+        Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
+        clazz
+            .methods()
+            .forEach(
+                m -> {
+                  if (m.isInitializer()) {
+                    clearEnumToUnboxMethod(m);
+                  } else {
+                    DexType newHolder = relocator.getNewMemberLocationFor(clazz.type);
+                    List<DexEncodedMethod> movedMethods =
+                        unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
+                    movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
+                    methodsToRemove.add(m);
+                  }
+                });
+        clazz.getMethodCollection().removeMethods(methodsToRemove);
+      } else {
+        clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
+        fixupFields(clazz.staticFields(), clazz::setStaticField);
+        fixupFields(clazz.instanceFields(), clazz::setInstanceField);
+      }
+    }
+    for (DexType toUnbox : enumsToUnbox) {
+      lensBuilder.map(toUnbox, factory.intType);
+    }
+    unboxedEnumsMethods.forEach(
+        (newHolderType, movedMethods) -> {
+          DexProgramClass newHolderClass = appView.definitionFor(newHolderType).asProgramClass();
+          newHolderClass.addDirectMethods(movedMethods);
+        });
+    return lensBuilder.build(factory, appView.graphLens(), enumsToUnbox);
+  }
+
+  private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) {
+    // The compiler may have references to the enum methods, but such methods will be removed
+    // and they cannot be reprocessed since their rewriting through the lensCodeRewriter/
+    // enumUnboxerRewriter will generate invalid code.
+    // To work around this problem we clear such methods, i.e., we replace the code object by
+    // an empty throwing code object, so reprocessing won't take time and will be valid.
+    enumMethod.setCode(enumMethod.buildEmptyThrowingCode(appView.options()), appView);
+  }
+
+  private DexEncodedMethod fixupEncodedMethodToUtility(
+      DexEncodedMethod encodedMethod, DexType newHolder) {
+    DexMethod method = encodedMethod.method;
+    DexString newMethodName =
+        factory.createString(
+            enumUnboxerRewriter.compatibleName(method.holder)
+                + "$"
+                + (encodedMethod.isStatic() ? "s" : "v")
+                + "$"
+                + method.name.toString());
+    DexProto proto = encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
+    DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
+    assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
+    lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
+    encodedMethod.accessFlags.promoteToPublic();
+    encodedMethod.accessFlags.promoteToStatic();
+    encodedMethod.clearAnnotations();
+    encodedMethod.clearParameterAnnotations();
+    return encodedMethod.toTypeSubstitutedMethod(newMethod);
+  }
+
+  private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
+    DexProto newProto = fixupProto(encodedMethod.proto());
+    if (newProto == encodedMethod.proto()) {
+      return encodedMethod;
+    }
+    assert !encodedMethod.isClassInitializer();
+    // We add the $enumunboxing$ suffix to make sure we do not create a library override.
+    String newMethodName =
+        encodedMethod.getName().toString()
+            + (encodedMethod.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
+    DexMethod newMethod = factory.createMethod(encodedMethod.holder(), newProto, newMethodName);
+    newMethod = ensureUniqueMethod(encodedMethod, newMethod);
+    int numberOfExtraNullParameters = newMethod.getArity() - encodedMethod.method.getArity();
+    boolean isStatic = encodedMethod.isStatic();
+    lensBuilder.move(
+        encodedMethod.method, isStatic, newMethod, isStatic, numberOfExtraNullParameters);
+    DexEncodedMethod newEncodedMethod = encodedMethod.toTypeSubstitutedMethod(newMethod);
+    assert !encodedMethod.isLibraryMethodOverride().isTrue()
+        : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
+    if (newEncodedMethod.isNonPrivateVirtualMethod()) {
+      newEncodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+    }
+    return newEncodedMethod;
+  }
+
+  private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
+    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);
+      }
+    } else {
+      int index = 0;
+      while (holder.lookupMethod(newMethod) != null) {
+        newMethod =
+            factory.createMethod(
+                newMethod.holder,
+                newMethod.proto,
+                encodedMethod.getName().toString() + "$enumunboxing$" + index++);
+      }
+    }
+    return newMethod;
+  }
+
+  private void fixupFields(List<DexEncodedField> fields, DexClass.FieldSetter setter) {
+    if (fields == null) {
+      return;
+    }
+    for (int i = 0; i < fields.size(); i++) {
+      DexEncodedField encodedField = fields.get(i);
+      DexField field = encodedField.field;
+      DexType newType = fixupType(field.type);
+      if (newType != field.type) {
+        DexField newField = factory.createField(field.holder, newType, field.name);
+        lensBuilder.move(field, newField);
+        DexEncodedField newEncodedField = encodedField.toTypeSubstitutedField(newField);
+        setter.setField(i, newEncodedField);
+        if (encodedField.isStatic() && encodedField.hasExplicitStaticValue()) {
+          assert encodedField.getStaticValue() == DexValue.DexValueNull.NULL;
+          newEncodedField.setStaticValue(DexValue.DexValueInt.DEFAULT);
+          // TODO(b/150593449): Support conversion from DexValueEnum to DexValueInt.
+        }
+      }
+    }
+  }
+
+  private DexProto fixupProto(DexProto proto) {
+    DexType returnType = fixupType(proto.returnType);
+    DexType[] arguments = fixupTypes(proto.parameters.values);
+    return factory.createProto(returnType, arguments);
+  }
+
+  private DexType fixupType(DexType type) {
+    if (type.isArrayType()) {
+      DexType base = type.toBaseType(factory);
+      DexType fixed = fixupType(base);
+      if (base == fixed) {
+        return type;
+      }
+      return type.replaceBaseType(fixed, factory);
+    }
+    if (type.isClassType() && enumsToUnbox.contains(type)) {
+      DexType intType = factory.intType;
+      lensBuilder.map(type, intType);
+      return intType;
+    }
+    return type;
+  }
+
+  private DexType[] fixupTypes(DexType[] types) {
+    DexType[] result = new DexType[types.length];
+    for (int i = 0; i < result.length; i++) {
+      result[i] = fixupType(types[i]);
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
new file mode 100644
index 0000000..16c9118
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -0,0 +1,151 @@
+// 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.optimize.enums;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.ProgramPackage;
+import com.android.tools.r8.graph.ProgramPackageCollection;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class UnboxedEnumMemberRelocator {
+
+  public static final String ENUM_UNBOXING_UTILITY_CLASS_SUFFIX = "$r8$EnumUnboxingUtility";
+
+  // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
+  // methods (ordinal(I), equals(II), etc.) and the unboxed enums members which were free to be
+  // placed anywhere.
+  private final DexType defaultEnumUnboxingUtility;
+  // Some unboxed enum members have to be placed in a specific package, in this case, we keep a
+  // map from unboxed enum types to synthetic classes, so that all members of unboxed enums in the
+  // keys are moved to the corresponding value.
+  private final ImmutableMap<DexType, DexType> relocationMap;
+
+  public DexType getDefaultEnumUnboxingUtility() {
+    return defaultEnumUnboxingUtility;
+  }
+
+  public DexType getNewMemberLocationFor(DexType enumType) {
+    return relocationMap.getOrDefault(enumType, defaultEnumUnboxingUtility);
+  }
+
+  private UnboxedEnumMemberRelocator(
+      DexType defaultEnumUnboxingUtility, ImmutableMap<DexType, DexType> relocationMap) {
+    this.defaultEnumUnboxingUtility = defaultEnumUnboxingUtility;
+    this.relocationMap = relocationMap;
+  }
+
+  public static Builder builder(AppView<?> appView) {
+    return new Builder(appView);
+  }
+
+  public static class Builder {
+    private DexType defaultEnumUnboxingUtility;
+    private Map<DexType, DexType> relocationMap = new IdentityHashMap<>();
+    private final AppView<?> appView;
+
+    public Builder(AppView<?> appView) {
+      this.appView = appView;
+    }
+
+    public Builder synthesizeEnumUnboxingUtilityClasses(
+        Set<DexProgramClass> enumsToUnbox,
+        ProgramPackageCollection enumsToUnboxWithPackageRequirement,
+        DirectMappedDexApplication.Builder appBuilder) {
+      defaultEnumUnboxingUtility = synthesizeUtilityClass(enumsToUnbox, appBuilder);
+      if (!enumsToUnboxWithPackageRequirement.isEmpty()) {
+        synthesizeRelocationMap(enumsToUnboxWithPackageRequirement, appBuilder);
+      }
+      return this;
+    }
+
+    public UnboxedEnumMemberRelocator build() {
+      return new UnboxedEnumMemberRelocator(
+          defaultEnumUnboxingUtility, ImmutableMap.copyOf(relocationMap));
+    }
+
+    private void synthesizeRelocationMap(
+        ProgramPackageCollection enumsToUnboxWithPackageRequirement,
+        DirectMappedDexApplication.Builder appBuilder) {
+      for (ProgramPackage programPackage : enumsToUnboxWithPackageRequirement) {
+        Set<DexProgramClass> enumsToUnboxInPackage = programPackage.classesInPackage();
+        DexType utilityType = synthesizeUtilityClass(enumsToUnboxInPackage, appBuilder);
+        for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
+          assert !relocationMap.containsKey(enumToUnbox.type);
+          relocationMap.put(enumToUnbox.type, utilityType);
+        }
+      }
+    }
+
+    private DexType synthesizeUtilityClass(
+        Set<DexProgramClass> contexts, DirectMappedDexApplication.Builder appBuilder) {
+      DexType deterministicContextType = findDeterministicContextType(contexts);
+      assert deterministicContextType.isClassType();
+      String descriptorString = deterministicContextType.toDescriptorString();
+      String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
+      String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
+      DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
+      // The defaultEnumUnboxingUtility depends on all unboxable enums, and other synthetic types
+      // depend on a subset of the unboxable enums, the deterministicContextType can therefore
+      // be found twice, and in that case the same utility class can be used for both.
+      if (type == defaultEnumUnboxingUtility) {
+        return defaultEnumUnboxingUtility;
+      }
+      assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
+      DexProgramClass syntheticClass =
+          new DexProgramClass(
+              type,
+              null,
+              new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+              ClassAccessFlags.fromSharedAccessFlags(
+                  Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+              appView.dexItemFactory().objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              appView.dexItemFactory().getSkipNameValidationForTesting(),
+              DexProgramClass::checksumFromType);
+      appBuilder.addSynthesizedClass(syntheticClass);
+      appView
+          .appInfo()
+          .addSynthesizedClass(
+              syntheticClass, appView.appInfo().getMainDexClasses().containsAnyOf(contexts));
+      return syntheticClass.type;
+    }
+
+    private DexType findDeterministicContextType(Set<DexProgramClass> contexts) {
+      DexType deterministicContext = null;
+      for (DexProgramClass context : contexts) {
+        if (deterministicContext == null) {
+          deterministicContext = context.type;
+        } else if (context.type.slowCompareTo(deterministicContext) < 0) {
+          deterministicContext = context.type;
+        }
+      }
+      return deterministicContext;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index a7b1576..3414861 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -254,8 +254,7 @@
                 cls.getKotlinInfo().isSyntheticClass()
                     && cls.getKotlinInfo().asSyntheticClass().isLambda()
                     && KotlinLambdaGroupIdFactory.hasValidAnnotations(kotlin, cls)
-                    && (appView.options().featureSplitConfiguration == null
-                        || !appView.options().featureSplitConfiguration.isInFeature(cls)))
+                    && !appView.appInfo().getClassToFeatureSplitMap().isInFeature(cls))
         .sorted((a, b) -> a.type.slowCompareTo(b.type)) // Ensure stable ordering.
         .forEachOrdered(
             lambda -> {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 0372a33..0231d43 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -15,7 +15,6 @@
 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.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -38,7 +37,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.ListIterator;
@@ -132,23 +130,28 @@
                 DexType type = field.field.type;
                 if (singletonFields.put(type, field) != null) {
                   // There is already candidate singleton field found.
-                  notEligible.add(type);
+                  markNotEligible(type, notEligible);
                 }
               }
 
               // Don't allow fields with this candidate types.
               for (DexEncodedField field : cls.instanceFields()) {
-                notEligible.add(field.field.type);
+                markNotEligible(field.field.type, notEligible);
               }
 
               // Don't allow methods that take a value of this type.
               for (DexEncodedMethod method : cls.methods()) {
-                DexProto proto = method.method.proto;
-                notEligible.addAll(Arrays.asList(proto.parameters.values));
+                for (DexType parameter : method.getProto().parameters.values) {
+                  markNotEligible(parameter, notEligible);
+                }
+                if (method.isSynchronized()) {
+                  markNotEligible(cls.type, notEligible);
+                }
               }
 
               // High-level limitations on what classes we consider eligible.
-              if (cls.isInterface() // Must not be an interface or an abstract class.
+              if (cls.isInterface()
+                  // Must not be an interface or an abstract class.
                   || cls.accessFlags.isAbstract()
                   // Don't support candidates with instance fields
                   || cls.instanceFields().size() > 0
@@ -159,7 +162,7 @@
                   // Staticizing classes implementing interfaces is more
                   // difficult, so don't support it until we really need it.
                   || !cls.interfaces.isEmpty()) {
-                notEligible.add(cls.type);
+                markNotEligible(cls.type, notEligible);
               }
             });
 
@@ -180,6 +183,12 @@
     });
   }
 
+  private void markNotEligible(DexType type, Set<DexType> notEligible) {
+    if (type.isClassType()) {
+      notEligible.add(type);
+    }
+  }
+
   private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.field)) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 477f516..012bd0e 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -2262,8 +2262,9 @@
     }
     LiveIntervalsUse firstUseWithLowerLimit = null;
     boolean hasUsesBeforeFirstUseWithLowerLimit = false;
+    int highestRegisterNumber = registerNumber + spilled.requiredRegisters() - 1;
     for (LiveIntervalsUse use : spilled.getUses()) {
-      if (registerNumber > use.getLimit()) {
+      if (highestRegisterNumber > use.getLimit()) {
         firstUseWithLowerLimit = use;
         break;
       } else {
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index ec34d75..e13331f 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
@@ -113,6 +114,7 @@
         marker.isRelocator() ? Optional.empty() : Optional.of(marker.toString());
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
     for (DexProgramClass clazz : application.classes()) {
+      assert SyntheticItems.verifyNotInternalSynthetic(clazz.getType());
       if (clazz.getSynthesizedFrom().isEmpty()
           || options.isDesugaredLibraryCompilation()
           || options.cfToCfDesugar) {
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 5163a17..848008c 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -63,7 +63,7 @@
     // For each package, find the set of classes that can be repackaged, and move them to the
     // desired namespace.
     Map<DexType, DexType> mappings = new IdentityHashMap<>();
-    for (ProgramPackage pkg : ProgramPackageCollection.create(appView)) {
+    for (ProgramPackage pkg : ProgramPackageCollection.createWithAllProgramClasses(appView)) {
       Iterable<DexProgramClass> classesToRepackage =
           computeClassesToRepackage(pkg, executorService);
       String newPackageDescriptor = getNewPackageDescriptor(pkg);
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 8e65f12..f34a5aa 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -49,7 +49,7 @@
 
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
-          "Usage: retrace <proguard-map> <stacktrace-file> [--regex <regexp>, --verbose, --info]",
+          "Usage: retrace <proguard-map> [stack-trace-file] [--regex <regexp>, --verbose, --info]",
           "  where <proguard-map> is an r8 generated mapping file.");
 
   private static Builder parseArguments(String[] args, DiagnosticsHandler diagnosticsHandler) {
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 6c6bab5..627d84f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -40,14 +41,13 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.graph.SyntheticItems;
-import com.android.tools.r8.graph.SyntheticItems.CommittedItems;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -163,8 +163,11 @@
   public final PredicateSet<DexType> alwaysClassInline;
   /** All types that *must* never be inlined due to a configuration directive (testing only). */
   public final Set<DexType> neverClassInline;
-  /** All types that *must* never be merged due to a configuration directive (testing only). */
-  public final Set<DexType> neverMerge;
+
+  private final Set<DexType> noVerticalClassMerging;
+  private final Set<DexType> noHorizontalClassMerging;
+  private final Set<DexType> noStaticClassMerging;
+
   /** Set of const-class references. */
   public final Set<DexType> constClassReferences;
   /**
@@ -195,9 +198,9 @@
 
   // TODO(zerny): Clean up the constructors so we have just one.
   AppInfoWithLiveness(
-      DirectMappedDexApplication application,
+      CommittedItems syntheticItems,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
       MainDexClasses mainDexClasses,
-      SyntheticItems.CommittedItems syntheticItems,
       Set<DexType> deadProtoTypes,
       Set<DexType> missingTypes,
       Set<DexType> liveTypes,
@@ -230,7 +233,9 @@
       Set<DexMethod> neverReprocess,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
-      Set<DexType> neverMerge,
+      Set<DexType> noVerticalClassMerging,
+      Set<DexType> noHorizontalClassMerging,
+      Set<DexType> noStaticClassMerging,
       Set<DexReference> neverPropagateValue,
       Object2BooleanMap<DexReference> identifierNameStrings,
       Set<DexType> prunedTypes,
@@ -238,7 +243,7 @@
       EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> constClassReferences,
       Map<DexType, Visibility> initClassReferences) {
-    super(application, mainDexClasses, syntheticItems);
+    super(syntheticItems, classToFeatureSplitMap, mainDexClasses);
     this.deadProtoTypes = deadProtoTypes;
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
@@ -271,7 +276,9 @@
     this.neverReprocess = neverReprocess;
     this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
-    this.neverMerge = neverMerge;
+    this.noVerticalClassMerging = noVerticalClassMerging;
+    this.noHorizontalClassMerging = noHorizontalClassMerging;
+    this.noStaticClassMerging = noStaticClassMerging;
     this.neverPropagateValue = neverPropagateValue;
     this.identifierNameStrings = identifierNameStrings;
     this.prunedTypes = prunedTypes;
@@ -315,7 +322,9 @@
       Set<DexMethod> neverReprocess,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
-      Set<DexType> neverMerge,
+      Set<DexType> noVerticalClassMerging,
+      Set<DexType> noHorizontalClassMerging,
+      Set<DexType> noStaticClassMerging,
       Set<DexReference> neverPropagateValue,
       Object2BooleanMap<DexReference> identifierNameStrings,
       Set<DexType> prunedTypes,
@@ -324,9 +333,9 @@
       Set<DexType> constClassReferences,
       Map<DexType, Visibility> initClassReferences) {
     super(
-        appInfoWithClassHierarchy.app(),
-        appInfoWithClassHierarchy.getMainDexClasses(),
-        appInfoWithClassHierarchy.getSyntheticItems().commit(appInfoWithClassHierarchy.app()));
+        appInfoWithClassHierarchy.getSyntheticItems().commit(appInfoWithClassHierarchy.app()),
+        appInfoWithClassHierarchy.getClassToFeatureSplitMap(),
+        appInfoWithClassHierarchy.getMainDexClasses());
     this.deadProtoTypes = deadProtoTypes;
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
@@ -359,7 +368,9 @@
     this.neverReprocess = neverReprocess;
     this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
-    this.neverMerge = neverMerge;
+    this.noVerticalClassMerging = noVerticalClassMerging;
+    this.noHorizontalClassMerging = noHorizontalClassMerging;
+    this.noStaticClassMerging = noStaticClassMerging;
     this.neverPropagateValue = neverPropagateValue;
     this.identifierNameStrings = identifierNameStrings;
     this.prunedTypes = prunedTypes;
@@ -369,12 +380,16 @@
     this.initClassReferences = initClassReferences;
   }
 
-  private AppInfoWithLiveness(AppInfoWithLiveness previous) {
+  private AppInfoWithLiveness(
+      AppInfoWithLiveness previous, CommittedItems committedItems, Set<DexType> removedTypes) {
     this(
-        previous,
+        committedItems,
+        previous.getClassToFeatureSplitMap(),
+        previous.getMainDexClasses(),
         previous.deadProtoTypes,
         previous.missingTypes,
-        previous.liveTypes,
+        CollectionUtils.mergeSets(
+            Sets.difference(previous.liveTypes, removedTypes), committedItems.getCommittedTypes()),
         previous.instantiatedAppServices,
         previous.targetedMethods,
         previous.failedResolutionTargets,
@@ -404,7 +419,9 @@
         previous.neverReprocess,
         previous.alwaysClassInline,
         previous.neverClassInline,
-        previous.neverMerge,
+        previous.noVerticalClassMerging,
+        previous.noHorizontalClassMerging,
+        previous.noStaticClassMerging,
         previous.neverPropagateValue,
         previous.identifierNameStrings,
         previous.prunedTypes,
@@ -417,15 +434,17 @@
   private AppInfoWithLiveness(
       AppInfoWithLiveness previous,
       DirectMappedDexApplication application,
-      Collection<DexType> removedClasses,
+      Set<DexType> removedClasses,
       Collection<DexReference> additionalPinnedItems) {
     this(
-        application,
-        previous.getMainDexClasses(),
-        previous.getSyntheticItems().commit(application),
+        previous.getSyntheticItems().commitPrunedClasses(application, removedClasses),
+        previous.getClassToFeatureSplitMap().withoutPrunedClasses(removedClasses),
+        previous.getMainDexClasses().withoutPrunedClasses(removedClasses),
         previous.deadProtoTypes,
         previous.missingTypes,
-        previous.liveTypes,
+        removedClasses == null
+            ? previous.liveTypes
+            : Sets.difference(previous.liveTypes, removedClasses),
         previous.instantiatedAppServices,
         previous.targetedMethods,
         previous.failedResolutionTargets,
@@ -455,7 +474,9 @@
         previous.neverReprocess,
         previous.alwaysClassInline,
         previous.neverClassInline,
-        previous.neverMerge,
+        previous.noVerticalClassMerging,
+        previous.noHorizontalClassMerging,
+        previous.noStaticClassMerging,
         previous.neverPropagateValue,
         previous.identifierNameStrings,
         removedClasses == null
@@ -510,9 +531,9 @@
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps) {
     super(
-        previous.app(),
-        previous.getMainDexClasses(),
-        previous.getSyntheticItems().commit(previous.app()));
+        previous.getSyntheticItems().commit(previous.app()),
+        previous.getClassToFeatureSplitMap(),
+        previous.getMainDexClasses());
     this.deadProtoTypes = previous.deadProtoTypes;
     this.missingTypes = previous.missingTypes;
     this.liveTypes = previous.liveTypes;
@@ -545,7 +566,9 @@
     this.neverReprocess = previous.neverReprocess;
     this.alwaysClassInline = previous.alwaysClassInline;
     this.neverClassInline = previous.neverClassInline;
-    this.neverMerge = previous.neverMerge;
+    this.noVerticalClassMerging = previous.noVerticalClassMerging;
+    this.noHorizontalClassMerging = previous.noHorizontalClassMerging;
+    this.noStaticClassMerging = previous.noStaticClassMerging;
     this.neverPropagateValue = previous.neverPropagateValue;
     this.identifierNameStrings = previous.identifierNameStrings;
     this.prunedTypes = previous.prunedTypes;
@@ -570,8 +593,6 @@
             || InterfaceMethodRewriter.isCompanionClassType(type)
             || InterfaceMethodRewriter.hasDispatchClassSuffix(type)
             || InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
-            || type.toDescriptorString().startsWith("L$r8$backportedMethods$")
-            || type.toDescriptorString().startsWith("Lj$/$r8$backportedMethods$")
             || type.toDescriptorString().startsWith("Lj$/$r8$retargetLibraryMember$")
             || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
             // TODO(b/150736225): Not sure how to remove these.
@@ -956,7 +977,7 @@
    */
   public AppInfoWithLiveness prunedCopyFrom(
       DirectMappedDexApplication application,
-      Collection<DexType> removedClasses,
+      Set<DexType> removedClasses,
       Collection<DexReference> additionalPinnedItems) {
     assert checkIfObsolete();
     if (!removedClasses.isEmpty()) {
@@ -966,6 +987,11 @@
     return new AppInfoWithLiveness(this, application, removedClasses, additionalPinnedItems);
   }
 
+  public AppInfoWithLiveness rebuildWithLiveness(
+      CommittedItems committedItems, Set<DexType> removedTypes) {
+    return new AppInfoWithLiveness(this, committedItems, removedTypes);
+  }
+
   public AppInfoWithLiveness rewrittenWithLens(
       DirectMappedDexApplication application, NestedGraphLens lens) {
     assert checkIfObsolete();
@@ -986,12 +1012,12 @@
             .map(FieldResolutionResult::getResolvedField)
             .collect(Collectors.toList()));
 
-    CommittedItems committedItems = getSyntheticItems().commit(application, lens);
+    CommittedItems committedItems = getSyntheticItems().commitRewrittenWithLens(application, lens);
     DexDefinitionSupplier definitionSupplier = application.getDefinitionsSupplier(committedItems);
     return new AppInfoWithLiveness(
-        application,
-        getMainDexClasses().rewrittenWithLens(lens),
         committedItems,
+        getClassToFeatureSplitMap().rewrittenWithLens(lens),
+        getMainDexClasses().rewrittenWithLens(lens),
         deadProtoTypes,
         missingTypes,
         lens.rewriteTypes(liveTypes),
@@ -1028,7 +1054,9 @@
         lens.rewriteMethods(neverReprocess),
         alwaysClassInline.rewriteItems(lens::lookupType),
         lens.rewriteTypes(neverClassInline),
-        lens.rewriteTypes(neverMerge),
+        lens.rewriteTypes(noVerticalClassMerging),
+        lens.rewriteTypes(noHorizontalClassMerging),
+        lens.rewriteTypes(noStaticClassMerging),
         lens.rewriteReferences(neverPropagateValue),
         lens.rewriteReferenceKeys(identifierNameStrings),
         // Don't rewrite pruned types - the removed types are identified by their original name.
@@ -1402,4 +1430,28 @@
             this)
         .shouldBreak();
   }
+
+  /**
+   * All types that *must* never be merged vertically due to a configuration directive (testing
+   * only).
+   */
+  public Set<DexType> getNoVerticalClassMergingSet() {
+    return noVerticalClassMerging;
+  }
+
+  /**
+   * All types that *must* never be merged horizontally due to a configuration directive (testing
+   * only).
+   */
+  public Set<DexType> getNoHorizontalClassMergingSet() {
+    return noHorizontalClassMerging;
+  }
+
+  /**
+   * All types that *must* never be merged by the static class merger due to a configuration
+   * directive (testing only).
+   */
+  public Set<DexType> getNoStaticClassMergingSet() {
+    return noStaticClassMerging;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index d692cef..98def17 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2743,7 +2743,7 @@
   }
 
   public NestedGraphLens buildGraphLens(AppView<?> appView) {
-    return lambdaRewriter != null ? lambdaRewriter.buildMappingLens(appView) : null;
+    return lambdaRewriter != null ? lambdaRewriter.fixup() : null;
   }
 
   private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
@@ -2885,7 +2885,7 @@
 
     // Now all additions are computed, the application is atomically extended with those additions.
     appInfo =
-        appInfo.rebuild(
+        appInfo.rebuildWithClassHierarchy(
             app -> {
               Builder appBuilder = app.asDirect().builder();
               additions.amendApplication(appBuilder);
@@ -3019,9 +3019,9 @@
 
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
-            app,
-            appInfo.getMainDexClasses(),
             appInfo.getSyntheticItems().commit(app),
+            appInfo.getClassToFeatureSplitMap(),
+            appInfo.getMainDexClasses(),
             deadProtoTypes,
             mode.isFinalTreeShaking()
                 ? Sets.union(initialMissingTypes, missingTypes)
@@ -3059,7 +3059,9 @@
             rootSet.neverReprocess,
             rootSet.alwaysClassInline,
             rootSet.neverClassInline,
-            rootSet.neverMerge,
+            rootSet.noVerticalClassMerging,
+            rootSet.noHorizontalClassMerging,
+            rootSet.noStaticClassMerging,
             rootSet.neverPropagateValue,
             joinIdentifierNameStrings(rootSet.identifierNameStrings, identifierNameStrings),
             Collections.emptySet(),
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 8841f15..18709f3 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -300,6 +300,13 @@
       if (neverInlineRuleForCondition != null) {
         rootSetBuilder.runPerRule(executorService, futures, neverInlineRuleForCondition, null);
       }
+
+      // Prevent horizontal class merging of any -if rule members.
+      NoHorizontalClassMergingRule noHorizontalClassMergingRule =
+          materializedRule.noHorizontalClassMergingRuleForCondition(dexItemFactory);
+      if (noHorizontalClassMergingRule != null) {
+        rootSetBuilder.runPerRule(executorService, futures, noHorizontalClassMergingRule, null);
+      }
     }
 
     // Keep whatever is required by the -if rule.
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
index cfadae32..67cd3b8 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
@@ -83,6 +83,16 @@
     return mainDexClasses.size();
   }
 
+  public MainDexClasses withoutPrunedClasses(Set<DexType> prunedClasses) {
+    MainDexClasses mainDexClassesAfterPruning = createEmptyMainDexClasses();
+    for (DexType mainDexClass : mainDexClasses) {
+      if (!prunedClasses.contains(mainDexClass)) {
+        mainDexClassesAfterPruning.mainDexClasses.add(mainDexClass);
+      }
+    }
+    return mainDexClassesAfterPruning;
+  }
+
   public static class Builder {
 
     private final Set<DexType> mainDexClasses = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/NoHorizontalClassMergingRule.java
similarity index 65%
copy from src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
copy to src/main/java/com/android/tools/r8/shaking/NoHorizontalClassMergingRule.java
index ee57043..acaedac 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/NoHorizontalClassMergingRule.java
@@ -1,40 +1,32 @@
-// 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 com.android.tools.r8.shaking;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import java.util.List;
 
-public class ClassMergingRule extends ProguardConfigurationRule {
+public class NoHorizontalClassMergingRule extends ProguardConfigurationRule {
 
-  public enum Type {
-    NEVER
-  }
+  public static final String RULE_NAME = "nohorizontalclassmerging";
 
-  public static class Builder extends ProguardConfigurationRule.Builder<ClassMergingRule, Builder> {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<NoHorizontalClassMergingRule, Builder> {
 
     private Builder() {
       super();
     }
 
-    Type type;
-
     @Override
-    public Builder self() {
-      return this;
-    }
-
-    public Builder setType(Type type) {
-      this.type = type;
+    public NoHorizontalClassMergingRule.Builder self() {
       return this;
     }
 
     @Override
-    public ClassMergingRule build() {
-      return new ClassMergingRule(
+    public NoHorizontalClassMergingRule build() {
+      return new NoHorizontalClassMergingRule(
           origin,
           getPosition(),
           source,
@@ -47,14 +39,11 @@
           buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
-          memberRules,
-          type);
+          memberRules);
     }
   }
 
-  private final Type type;
-
-  private ClassMergingRule(
+  private NoHorizontalClassMergingRule(
       Origin origin,
       Position position,
       String source,
@@ -67,8 +56,7 @@
       List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
-      List<ProguardMemberRule> memberRules,
-      Type type) {
+      List<ProguardMemberRule> memberRules) {
     super(
         origin,
         position,
@@ -83,23 +71,14 @@
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
-    this.type = type;
   }
 
   public static Builder builder() {
     return new Builder();
   }
 
-  public Type getType() {
-    return type;
-  }
-
   @Override
   String typeString() {
-    switch (type) {
-      case NEVER:
-        return "nevermerge";
-    }
-    throw new Unreachable("Unknown class merging type " + type);
+    return RULE_NAME;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java
similarity index 65%
copy from src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
copy to src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java
index ee57043..dc19834 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java
@@ -1,40 +1,31 @@
-// 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 com.android.tools.r8.shaking;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import java.util.List;
 
-public class ClassMergingRule extends ProguardConfigurationRule {
+public class NoStaticClassMergingRule extends ProguardConfigurationRule {
+  public static final String RULE_NAME = "nostaticclassmerging";
 
-  public enum Type {
-    NEVER
-  }
-
-  public static class Builder extends ProguardConfigurationRule.Builder<ClassMergingRule, Builder> {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<NoStaticClassMergingRule, Builder> {
 
     private Builder() {
       super();
     }
 
-    Type type;
-
     @Override
-    public Builder self() {
-      return this;
-    }
-
-    public Builder setType(Type type) {
-      this.type = type;
+    public NoStaticClassMergingRule.Builder self() {
       return this;
     }
 
     @Override
-    public ClassMergingRule build() {
-      return new ClassMergingRule(
+    public NoStaticClassMergingRule build() {
+      return new NoStaticClassMergingRule(
           origin,
           getPosition(),
           source,
@@ -47,14 +38,11 @@
           buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
-          memberRules,
-          type);
+          memberRules);
     }
   }
 
-  private final Type type;
-
-  private ClassMergingRule(
+  private NoStaticClassMergingRule(
       Origin origin,
       Position position,
       String source,
@@ -67,8 +55,7 @@
       List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
-      List<ProguardMemberRule> memberRules,
-      Type type) {
+      List<ProguardMemberRule> memberRules) {
     super(
         origin,
         position,
@@ -83,23 +70,14 @@
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
-    this.type = type;
   }
 
   public static Builder builder() {
     return new Builder();
   }
 
-  public Type getType() {
-    return type;
-  }
-
   @Override
   String typeString() {
-    switch (type) {
-      case NEVER:
-        return "nevermerge";
-    }
-    throw new Unreachable("Unknown class merging type " + type);
+    return RULE_NAME;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/NoVerticalClassMergingRule.java
similarity index 68%
rename from src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
rename to src/main/java/com/android/tools/r8/shaking/NoVerticalClassMergingRule.java
index ee57043..edda33c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/NoVerticalClassMergingRule.java
@@ -3,38 +3,29 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import java.util.List;
 
-public class ClassMergingRule extends ProguardConfigurationRule {
+public class NoVerticalClassMergingRule extends ProguardConfigurationRule {
 
-  public enum Type {
-    NEVER
-  }
+  public static final String RULE_NAME = "noverticalclassmerging";
 
-  public static class Builder extends ProguardConfigurationRule.Builder<ClassMergingRule, Builder> {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<NoVerticalClassMergingRule, Builder> {
 
     private Builder() {
       super();
     }
 
-    Type type;
-
     @Override
     public Builder self() {
       return this;
     }
 
-    public Builder setType(Type type) {
-      this.type = type;
-      return this;
-    }
-
     @Override
-    public ClassMergingRule build() {
-      return new ClassMergingRule(
+    public NoVerticalClassMergingRule build() {
+      return new NoVerticalClassMergingRule(
           origin,
           getPosition(),
           source,
@@ -47,14 +38,11 @@
           buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
-          memberRules,
-          type);
+          memberRules);
     }
   }
 
-  private final Type type;
-
-  private ClassMergingRule(
+  private NoVerticalClassMergingRule(
       Origin origin,
       Position position,
       String source,
@@ -67,8 +55,7 @@
       List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
-      List<ProguardMemberRule> memberRules,
-      Type type) {
+      List<ProguardMemberRule> memberRules) {
     super(
         origin,
         position,
@@ -83,23 +70,14 @@
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
-    this.type = type;
   }
 
   public static Builder builder() {
     return new Builder();
   }
 
-  public Type getType() {
-    return type;
-  }
-
   @Override
   String typeString() {
-    switch (type) {
-      case NEVER:
-        return "nevermerge";
-    }
-    throw new Unreachable("Unknown class merging type " + type);
+    return RULE_NAME;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 2bf270a..9269a2a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -92,8 +92,9 @@
       return inheritanceIsExtends;
     }
 
-    public void setInheritanceIsExtends(boolean inheritanceIsExtends) {
+    public B setInheritanceIsExtends(boolean inheritanceIsExtends) {
       this.inheritanceIsExtends = inheritanceIsExtends;
+      return self();
     }
 
     public boolean hasInheritanceClassName() {
@@ -104,13 +105,15 @@
       return inheritanceClassName;
     }
 
-    public void setInheritanceClassName(ProguardTypeMatcher inheritanceClassName) {
+    public B setInheritanceClassName(ProguardTypeMatcher inheritanceClassName) {
       this.inheritanceClassName = inheritanceClassName;
+      return self();
     }
 
-    public void addInheritanceAnnotations(List<ProguardTypeMatcher> inheritanceAnnotations) {
+    public B addInheritanceAnnotations(List<ProguardTypeMatcher> inheritanceAnnotations) {
       assert inheritanceAnnotations != null;
       this.inheritanceAnnotations.addAll(inheritanceAnnotations);
+      return self();
     }
 
     public List<ProguardTypeMatcher> buildInheritanceAnnotations() {
@@ -139,33 +142,38 @@
       return classTypeNegated;
     }
 
-    public void setClassTypeNegated(boolean classTypeNegated) {
+    public B setClassTypeNegated(boolean classTypeNegated) {
       this.classTypeNegated = classTypeNegated;
+      return self();
     }
 
     public ProguardAccessFlags getClassAccessFlags() {
       return classAccessFlags;
     }
 
-    public void setClassAccessFlags(ProguardAccessFlags flags) {
+    public B setClassAccessFlags(ProguardAccessFlags flags) {
       classAccessFlags = flags;
+      return self();
     }
 
     public ProguardAccessFlags getNegatedClassAccessFlags() {
       return negatedClassAccessFlags;
     }
 
-    public void setNegatedClassAccessFlags(ProguardAccessFlags flags) {
+    public B setNegatedClassAccessFlags(ProguardAccessFlags flags) {
       negatedClassAccessFlags = flags;
+      return self();
     }
 
-    public void addClassAnnotation(ProguardTypeMatcher classAnnotation) {
+    public B addClassAnnotation(ProguardTypeMatcher classAnnotation) {
       classAnnotations.add(classAnnotation);
+      return self();
     }
 
-    public void addClassAnnotations(List<ProguardTypeMatcher> classAnnotations) {
+    public B addClassAnnotations(List<ProguardTypeMatcher> classAnnotations) {
       assert classAnnotations != null;
       this.classAnnotations.addAll(classAnnotations);
+      return self();
     }
 
     public List<ProguardTypeMatcher> buildClassAnnotations() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 6b5604e..d846f49 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -302,9 +302,9 @@
         parseClassFilter(configurationBuilder::addDontWarnPattern);
       } else if (acceptString("dontnote")) {
         parseClassFilter(configurationBuilder::addDontNotePattern);
-      } else if (acceptString("repackageclasses")) {
+      } else if (acceptString(REPACKAGE_CLASSES)) {
         if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.FLATTEN) {
-          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy", optionStart);
+          warnOverridingOptions(REPACKAGE_CLASSES, FLATTEN_PACKAGE_HIERARCHY, optionStart);
         }
         skipWhitespace();
         char quote = acceptQuoteIfPresent();
@@ -318,9 +318,9 @@
             configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
           }
         }
-      } else if (acceptString("flattenpackagehierarchy")) {
+      } else if (acceptString(FLATTEN_PACKAGE_HIERARCHY)) {
         if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
-          warnOverridingOptions("repackageclasses", "flattenpackagehierarchy", optionStart);
+          warnOverridingOptions(REPACKAGE_CLASSES, FLATTEN_PACKAGE_HIERARCHY, optionStart);
           skipWhitespace();
           if (isOptionalArgumentGiven()) {
             skipSingleArgument();
@@ -469,8 +469,18 @@
           configurationBuilder.addRule(rule);
           return true;
         }
-        if (acceptString("nevermerge")) {
-          ClassMergingRule rule = parseClassMergingRule(ClassMergingRule.Type.NEVER, optionStart);
+        if (acceptString(NoVerticalClassMergingRule.RULE_NAME)) {
+          ProguardConfigurationRule rule = parseNoVerticalClassMergingRule(optionStart);
+          configurationBuilder.addRule(rule);
+          return true;
+        }
+        if (acceptString(NoHorizontalClassMergingRule.RULE_NAME)) {
+          ProguardConfigurationRule rule = parseNoHorizontalClassMergingRule(optionStart);
+          configurationBuilder.addRule(rule);
+          return true;
+        }
+        if (acceptString(NoStaticClassMergingRule.RULE_NAME)) {
+          ProguardConfigurationRule rule = parseNoStaticClassMergingRule(optionStart);
           configurationBuilder.addRule(rule);
           return true;
         }
@@ -741,10 +751,32 @@
       return keepRuleBuilder.build();
     }
 
-    private ClassMergingRule parseClassMergingRule(ClassMergingRule.Type type, Position start)
+    private NoVerticalClassMergingRule parseNoVerticalClassMergingRule(Position start)
         throws ProguardRuleParserException {
-      ClassMergingRule.Builder keepRuleBuilder =
-          ClassMergingRule.builder().setOrigin(origin).setStart(start).setType(type);
+      NoVerticalClassMergingRule.Builder keepRuleBuilder =
+          NoVerticalClassMergingRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
+      return keepRuleBuilder.build();
+    }
+
+    private NoHorizontalClassMergingRule parseNoHorizontalClassMergingRule(Position start)
+        throws ProguardRuleParserException {
+      NoHorizontalClassMergingRule.Builder keepRuleBuilder =
+          NoHorizontalClassMergingRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
+      return keepRuleBuilder.build();
+    }
+
+    private NoStaticClassMergingRule parseNoStaticClassMergingRule(Position start)
+        throws ProguardRuleParserException {
+      NoStaticClassMergingRule.Builder keepRuleBuilder =
+          NoStaticClassMergingRule.builder().setOrigin(origin).setStart(start);
       parseClassSpec(keepRuleBuilder, false);
       Position end = getPosition();
       keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index c8e185f..8313325 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -15,7 +15,7 @@
 
 public class ProguardIfRule extends ProguardKeepRuleBase {
 
-  private static final Origin neverInlineOrigin =
+  private static final Origin NEVER_INLINE_ORIGIN =
       new Origin(Origin.root()) {
         @Override
         public String part() {
@@ -23,6 +23,14 @@
         }
       };
 
+  private static final Origin NO_HORIZONTAL_CLASS_MERGING_ORIGIN =
+      new Origin(Origin.root()) {
+        @Override
+        public String part() {
+          return "<SYNTHETIC_NO_HORIZONTAL_CLASS_MERGING_RULE>";
+        }
+      };
+
   private final Set<DexReference> preconditions;
   final ProguardKeepRule subsequentRule;
 
@@ -156,7 +164,7 @@
 
   protected ClassInlineRule neverClassInlineRuleForCondition(DexItemFactory dexItemFactory) {
     return new ClassInlineRule(
-        neverInlineOrigin,
+        NEVER_INLINE_ORIGIN,
         Position.UNKNOWN,
         null,
         ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
@@ -201,7 +209,7 @@
       return null;
     }
     return new InlineRule(
-        neverInlineOrigin,
+        NEVER_INLINE_ORIGIN,
         Position.UNKNOWN,
         null,
         ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
@@ -222,6 +230,37 @@
         InlineRule.Type.NEVER);
   }
 
+  protected NoHorizontalClassMergingRule noHorizontalClassMergingRuleForCondition(
+      DexItemFactory dexItemFactory) {
+    List<ProguardMemberRule> memberRules = null;
+    if (getMemberRules() != null) {
+      memberRules =
+          getMemberRules().stream()
+              .filter(rule -> rule.getRuleType().includesMethods())
+              .map(memberRule -> memberRule.materialize(dexItemFactory))
+              .collect(Collectors.toList());
+    }
+
+    return NoHorizontalClassMergingRule.builder()
+        .setOrigin(NO_HORIZONTAL_CLASS_MERGING_ORIGIN)
+        .addClassAnnotations(
+            ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory))
+        .setClassAccessFlags(getClassAccessFlags())
+        .setNegatedClassAccessFlags(getNegatedClassAccessFlags())
+        .setClassType(getClassType())
+        .setClassTypeNegated(getClassTypeNegated())
+        .setClassNames(getClassNames().materialize(dexItemFactory))
+        .addInheritanceAnnotations(
+            ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory))
+        .setInheritanceClassName(
+            getInheritanceClassName() == null
+                ? null
+                : getInheritanceClassName().materialize(dexItemFactory))
+        .setInheritanceIsExtends(getInheritanceIsExtends())
+        .setMemberRules(memberRules)
+        .build();
+  }
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardIfRule)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index db43676..f8b5987 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -97,7 +97,9 @@
   private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
   private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
   private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
-  private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
+  private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
+  private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
+  private final Set<DexType> noStaticClassMerging = Sets.newIdentityHashSet();
   private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
   private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
       new IdentityHashMap<>();
@@ -240,7 +242,9 @@
         || rule instanceof WhyAreYouNotInliningRule) {
       markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
     } else if (rule instanceof ClassInlineRule
-        || rule instanceof ClassMergingRule
+        || rule instanceof NoVerticalClassMergingRule
+        || rule instanceof NoHorizontalClassMergingRule
+        || rule instanceof NoStaticClassMergingRule
         || rule instanceof ReprocessClassInitializerRule) {
       if (allRulesSatisfied(memberKeepRules, clazz)) {
         markClass(clazz, rule, ifRule);
@@ -318,7 +322,9 @@
           appView,
           subtypingInfo,
           alwaysClassInline,
-          neverMerge,
+          noVerticalClassMerging,
+          noHorizontalClassMerging,
+          noStaticClassMerging,
           alwaysInline,
           bypassClinitforInlining);
     }
@@ -342,7 +348,9 @@
         neverReprocess,
         alwaysClassInline,
         neverClassInline,
-        neverMerge,
+        noVerticalClassMerging,
+        noHorizontalClassMerging,
+        noStaticClassMerging,
         neverPropagateValue,
         mayHaveSideEffects,
         noSideEffects,
@@ -1204,16 +1212,14 @@
           throw new Unreachable();
       }
       context.markAsUsed();
-    } else if (context instanceof ClassMergingRule) {
-      switch (((ClassMergingRule) context).getType()) {
-        case NEVER:
-          if (item.isDexClass()) {
-            neverMerge.add(item.asDexClass().type);
-          }
-          break;
-        default:
-          throw new Unreachable();
-      }
+    } else if (context instanceof NoVerticalClassMergingRule) {
+      noVerticalClassMerging.add(item.asDexClass().type);
+      context.markAsUsed();
+    } else if (context instanceof NoHorizontalClassMergingRule) {
+      noHorizontalClassMerging.add(item.asDexClass().type);
+      context.markAsUsed();
+    } else if (context instanceof NoStaticClassMergingRule) {
+      noStaticClassMerging.add(item.asDexClass().type);
       context.markAsUsed();
     } else if (context instanceof MemberValuePropagationRule) {
       switch (((MemberValuePropagationRule) context).getType()) {
@@ -1734,7 +1740,9 @@
     public final Set<DexMethod> reprocess;
     public final Set<DexMethod> neverReprocess;
     public final PredicateSet<DexType> alwaysClassInline;
-    public final Set<DexType> neverMerge;
+    public final Set<DexType> noVerticalClassMerging;
+    public final Set<DexType> noHorizontalClassMerging;
+    public final Set<DexType> noStaticClassMerging;
     public final Set<DexReference> neverPropagateValue;
     public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
     public final Map<DexReference, ProguardMemberRule> noSideEffects;
@@ -1758,7 +1766,9 @@
         Set<DexMethod> neverReprocess,
         PredicateSet<DexType> alwaysClassInline,
         Set<DexType> neverClassInline,
-        Set<DexType> neverMerge,
+        Set<DexType> noVerticalClassMerging,
+        Set<DexType> noHorizontalClassMerging,
+        Set<DexType> noStaticClassMerging,
         Set<DexReference> neverPropagateValue,
         Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
         Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -1787,7 +1797,9 @@
       this.reprocess = reprocess;
       this.neverReprocess = neverReprocess;
       this.alwaysClassInline = alwaysClassInline;
-      this.neverMerge = neverMerge;
+      this.noVerticalClassMerging = noVerticalClassMerging;
+      this.noHorizontalClassMerging = noHorizontalClassMerging;
+      this.noStaticClassMerging = noStaticClassMerging;
       this.neverPropagateValue = neverPropagateValue;
       this.mayHaveSideEffects = mayHaveSideEffects;
       this.noSideEffects = noSideEffects;
@@ -1859,7 +1871,9 @@
     }
 
     public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
-      pruneDeadReferences(neverMerge, definitions, enqueuer);
+      pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
+      pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
+      pruneDeadReferences(noStaticClassMerging, definitions, enqueuer);
       pruneDeadReferences(alwaysInline, definitions, enqueuer);
       pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 3725f5f..abd2a99 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.FeatureSplit;
-import com.android.tools.r8.features.FeatureSplitConfiguration;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -243,12 +243,12 @@
     return null;
   }
 
-  private FeatureSplitConfiguration getFeatureSplitConfiguration() {
-    return appView.options().featureSplitConfiguration;
+  private ClassToFeatureSplitMap getClassToFeatureSplitMap() {
+    return appView.appInfo().getClassToFeatureSplitMap();
   }
 
   private MergeGroup getMergeGroup(DexProgramClass clazz) {
-    if (appView.appInfo().neverMerge.contains(clazz.type)) {
+    if (appView.appInfo().getNoStaticClassMergingSet().contains(clazz.type)) {
       return MergeGroup.DONT_MERGE;
     }
     if (clazz.staticFields().size() + clazz.getMethodCollection().size() == 0) {
@@ -303,13 +303,8 @@
   }
 
   private MergeKey getMergeKey(DexProgramClass clazz, MergeGroup mergeGroup) {
-    FeatureSplitConfiguration featureSplitConfiguration = getFeatureSplitConfiguration();
-    FeatureSplit featureSplit =
-        featureSplitConfiguration != null
-            ? featureSplitConfiguration.getFeatureSplit(clazz)
-            : FeatureSplit.BASE;
     return new MergeKey(
-        featureSplit,
+        getClassToFeatureSplitMap().getFeatureSplit(clazz),
         mergeGroup,
         mayMergeAcrossPackageBoundaries(clazz)
             ? MergeKey.GLOBAL
@@ -452,8 +447,7 @@
     assert targetClass.accessFlags.isAtLeastAsVisibleAs(sourceClass.accessFlags);
     assert sourceClass.instanceFields().isEmpty();
     assert targetClass.instanceFields().isEmpty();
-    assert getFeatureSplitConfiguration() == null
-        || getFeatureSplitConfiguration().inSameFeatureOrBothInBase(sourceClass, targetClass);
+    assert getClassToFeatureSplitMap().isInSameFeatureOrBothInBase(sourceClass, targetClass);
 
     numberOfMergedClasses++;
 
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 5231fe6..6db253e 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -110,12 +110,7 @@
           Log.debug(getClass(), "Removing class: " + clazz);
         }
         prunedTypes.add(clazz.type);
-        // TODO(b/150118654): It would be nice to add something such as
-        //  clazz.type.isD8R8SynthesizedType, but such test is currently expensive since it is
-        //  based on strings, so we check only against the enum unboxing utility class.
-        if (clazz.type != appView.dexItemFactory().enumUnboxingUtilityType) {
-          unusedItemsPrinter.registerUnusedClass(clazz);
-        }
+        unusedItemsPrinter.registerUnusedClass(clazz);
       }
     }
     unusedItemsPrinter.finished();
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 2d21927..5948028 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
@@ -350,7 +349,7 @@
         || allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass)
         || appInfo.isPinned(sourceClass.type)
         || pinnedTypes.contains(sourceClass.type)
-        || appInfo.neverMerge.contains(sourceClass.type)) {
+        || appInfo.getNoVerticalClassMergingSet().contains(sourceClass.type)) {
       return false;
     }
 
@@ -358,10 +357,9 @@
         .map(DexEncodedMember::toReference)
         .noneMatch(appInfo::isPinned);
 
-    FeatureSplitConfiguration featureSplitConfiguration =
-        appView.options().featureSplitConfiguration;
-    if (featureSplitConfiguration != null
-        && !featureSplitConfiguration.inSameFeatureOrBothInBase(sourceClass, targetClass)) {
+    if (!appInfo
+        .getClassToFeatureSplitMap()
+        .isInSameFeatureOrBothInBase(sourceClass, targetClass)) {
       return false;
     }
     if (appView.appServices().allServiceTypes().contains(sourceClass.type)
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
new file mode 100644
index 0000000..f6a6245
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.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.synthesis;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.function.Function;
+
+/**
+ * Represents an application with a fully "committed" addition of synthetic items.
+ *
+ * <p>The committed application is used to rebuild application info (see AppInfo and its
+ * derivatives) with the associated additional information for the program at well defined points on
+ * the main thread.
+ *
+ * <p>A committed application will have no pending synthetics that are not defined in the program
+ * classes collection, and it must also satisfy that all synthetic types are indeed contained in the
+ * applications program class collection.
+ */
+public class CommittedItems implements SyntheticDefinitionsProvider {
+
+  // Immutable package accessible fields to allow SyntheticItems creation.
+  final DexApplication application;
+  final int nextSyntheticId;
+  final ImmutableSet<DexType> legacySyntheticTypes;
+  final ImmutableMap<DexType, SyntheticReference> syntheticItems;
+  final ImmutableList<DexType> committedTypes;
+
+  CommittedItems(
+      int nextSyntheticId,
+      DexApplication application,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ImmutableMap<DexType, SyntheticReference> syntheticItems,
+      ImmutableList<DexType> committedTypes) {
+    assert verifyTypesAreInApp(application, legacySyntheticTypes);
+    assert verifyTypesAreInApp(application, syntheticItems.keySet());
+    this.nextSyntheticId = nextSyntheticId;
+    this.application = application;
+    this.legacySyntheticTypes = legacySyntheticTypes;
+    this.syntheticItems = syntheticItems;
+    this.committedTypes = committedTypes;
+  }
+
+  // Conversion to a mutable synthetic items collection. Should only be used in AppInfo creation.
+  public SyntheticItems toSyntheticItems() {
+    return new SyntheticItems(this);
+  }
+
+  public DexApplication getApplication() {
+    return application;
+  }
+
+  public Collection<DexType> getCommittedTypes() {
+    return committedTypes;
+  }
+
+  @Override
+  public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
+    // All synthetic types are committed to the application so lookup is just the base lookup.
+    return baseDefinitionFor.apply(type);
+  }
+
+  private static boolean verifyTypesAreInApp(DexApplication app, Collection<DexType> types) {
+    for (DexType type : types) {
+      assert app.programDefinitionFor(type) != null : "Missing synthetic: " + type;
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
new file mode 100644
index 0000000..c03022e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -0,0 +1,36 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.origin.Origin;
+
+/**
+ * A synthesizing context is the input type and origin that gives rise to a synthetic item.
+ *
+ * <p>Note that a context can only itself be a synthetic item if it was provided as an input that
+ * was marked as synthetic already, in which case it is its own context. In other words,
+ *
+ * <pre>
+ *   for any synthetic item, I:
+ *     context(I) == holder(I)  iff  I is a synthetic input
+ * </pre>
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SynthesizingContext {
+  final DexType type;
+  final Origin origin;
+
+  SynthesizingContext(DexType type, Origin origin) {
+    this.type = type;
+    this.origin = origin;
+  }
+
+  SynthesizingContext rewrite(NestedGraphLens lens) {
+    DexType rewritten = lens.lookupType(type);
+    return rewritten == type ? this : new SynthesizingContext(type, origin);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
new file mode 100644
index 0000000..ad04a8d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -0,0 +1,117 @@
+// 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.synthesis;
+
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.origin.Origin;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class SyntheticClassBuilder {
+  private final DexItemFactory factory;
+
+  private final DexType type;
+  private final Origin origin;
+
+  private DexType superType;
+  private DexTypeList interfaces = DexTypeList.empty();
+
+  private int nextMethodId = 0;
+  private List<SyntheticMethodBuilder> methods = new ArrayList<>();
+
+  SyntheticClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
+    this.factory = factory;
+    this.type = type;
+    this.origin = context.origin;
+    this.superType = factory.objectType;
+  }
+
+  public DexItemFactory getFactory() {
+    return factory;
+  }
+
+  public DexType getType() {
+    return type;
+  }
+
+  private String getNextMethodName() {
+    return SyntheticItems.INTERNAL_SYNTHETIC_METHOD_PREFIX + nextMethodId++;
+  }
+
+  public SyntheticClassBuilder addMethod(Consumer<SyntheticMethodBuilder> fn) {
+    SyntheticMethodBuilder method = new SyntheticMethodBuilder(this, getNextMethodName());
+    fn.accept(method);
+    methods.add(method);
+    return this;
+  }
+
+  DexProgramClass build() {
+    ClassAccessFlags accessFlags =
+        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+    Kind originKind = null;
+    DexString sourceFile = null;
+    NestHostClassAttribute nestHost = null;
+    List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
+    EnclosingMethodAttribute enclosingMembers = null;
+    List<InnerClassAttribute> innerClasses = Collections.emptyList();
+    DexAnnotationSet classAnnotations = DexAnnotationSet.empty();
+    DexEncodedField[] staticFields = DexEncodedField.EMPTY_ARRAY;
+    DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY;
+    DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
+    DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
+    assert !methods.isEmpty();
+    List<DexEncodedMethod> directs = new ArrayList<>(methods.size());
+    List<DexEncodedMethod> virtuals = new ArrayList<>(methods.size());
+    for (SyntheticMethodBuilder builder : methods) {
+      DexEncodedMethod method = builder.build();
+      if (method.isNonPrivateVirtualMethod()) {
+        virtuals.add(method);
+      } else {
+        directs.add(method);
+      }
+    }
+    if (!directs.isEmpty()) {
+      directMethods = directs.toArray(new DexEncodedMethod[directs.size()]);
+    }
+    if (!virtuals.isEmpty()) {
+      virtualMethods = virtuals.toArray(new DexEncodedMethod[virtuals.size()]);
+    }
+    long checksum = 7 * (long) directs.hashCode() + 11 * (long) virtuals.hashCode();
+    return new DexProgramClass(
+        type,
+        originKind,
+        origin,
+        accessFlags,
+        superType,
+        interfaces,
+        sourceFile,
+        nestHost,
+        nestMembers,
+        enclosingMembers,
+        innerClasses,
+        classAnnotations,
+        staticFields,
+        instanceFields,
+        directMethods,
+        virtualMethods,
+        factory.getSkipNameValidationForTesting(),
+        c -> checksum);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
new file mode 100644
index 0000000..daf936d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -0,0 +1,42 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.hash.HashCode;
+
+/**
+ * Base type for the defintion of a synthetic item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+abstract class SyntheticDefinition {
+  private final SynthesizingContext context;
+
+  SyntheticDefinition(SynthesizingContext context) {
+    this.context = context;
+  }
+
+  abstract SyntheticReference toReference();
+
+  SynthesizingContext getContext() {
+    return context;
+  }
+
+  DexType getContextType() {
+    return context.type;
+  }
+
+  Origin getContextOrigin() {
+    return context.origin;
+  }
+
+  abstract DexProgramClass getHolder();
+
+  abstract HashCode computeHash();
+
+  abstract boolean isEquivalentTo(SyntheticDefinition other);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/SyntheticDefinitionsProvider.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinitionsProvider.java
similarity index 75%
rename from src/main/java/com/android/tools/r8/graph/SyntheticDefinitionsProvider.java
rename to src/main/java/com/android/tools/r8/synthesis/SyntheticDefinitionsProvider.java
index 82e37ab..e66401e 100644
--- a/src/main/java/com/android/tools/r8/graph/SyntheticDefinitionsProvider.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinitionsProvider.java
@@ -1,8 +1,10 @@
 // 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.graph;
+package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
 import java.util.function.Function;
 
 public interface SyntheticDefinitionsProvider {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
new file mode 100644
index 0000000..a2dd07e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -0,0 +1,364 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.Builder;
+import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.common.hash.HashCode;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.function.Predicate;
+
+public class SyntheticFinalization {
+
+  public static class Result {
+    public final CommittedItems commit;
+    public final ImmutableSet<DexType> removedSyntheticClasses;
+
+    public Result(CommittedItems commit, ImmutableSet<DexType> removedSyntheticClasses) {
+      this.commit = commit;
+      this.removedSyntheticClasses = removedSyntheticClasses;
+    }
+  }
+
+  private static class EquivalenceGroup<T extends SyntheticDefinition & Comparable<T>>
+      implements Comparable<EquivalenceGroup<T>> {
+    private List<T> members;
+
+    EquivalenceGroup(T singleton) {
+      this(singleton, Collections.singletonList(singleton));
+    }
+
+    EquivalenceGroup(T representative, List<T> members) {
+      assert !members.isEmpty();
+      assert members.get(0) == representative;
+      this.members = members;
+    }
+
+    T getRepresentative() {
+      return members.get(0);
+    }
+
+    public List<T> getMembers() {
+      return members;
+    }
+
+    @Override
+    public int compareTo(EquivalenceGroup<T> other) {
+      return getRepresentative().compareTo(other.getRepresentative());
+    }
+  }
+
+  private final InternalOptions options;
+  private final ImmutableSet<DexType> legacySyntheticTypes;
+  private final ImmutableMap<DexType, SyntheticReference> syntheticItems;
+
+  SyntheticFinalization(
+      InternalOptions options,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ImmutableMap<DexType, SyntheticReference> syntheticItems) {
+    this.options = options;
+    this.legacySyntheticTypes = legacySyntheticTypes;
+    this.syntheticItems = syntheticItems;
+  }
+
+  public Result computeFinalSynthetics(AppView<?> appView) {
+    assert verifyNoNestedSynthetics();
+    DexApplication application = appView.appInfo().app();
+    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
+    GraphLens graphLens = appView.graphLens();
+
+    List<SyntheticMethodDefinition> methodDefinitions =
+        lookupSyntheticMethodDefinitions(application);
+
+    Map<HashCode, List<SyntheticMethodDefinition>> potentialEquivalences =
+        computePotentialEquivalences(methodDefinitions);
+
+    Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
+        computeActualEquivalences(potentialEquivalences, options.itemFactory);
+
+    Builder lensBuilder = GraphLens.builder();
+    List<DexProgramClass> newProgramClasses = new ArrayList<>();
+    List<DexProgramClass> finalSyntheticClasses = new ArrayList<>();
+    buildLensAndProgram(
+        application,
+        equivalences,
+        syntheticItems::containsKey,
+        mainDexClasses,
+        lensBuilder,
+        options.itemFactory,
+        newProgramClasses,
+        finalSyntheticClasses);
+
+    newProgramClasses.addAll(finalSyntheticClasses);
+
+    handleSynthesizedClassMapping(finalSyntheticClasses, application, options, mainDexClasses);
+
+    DexApplication app = application.builder().replaceProgramClasses(newProgramClasses).build();
+
+    appView.setGraphLens(lensBuilder.build(options.itemFactory, graphLens));
+    assert appView.appInfo().getMainDexClasses() == mainDexClasses;
+    return new Result(
+        new CommittedItems(
+            SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
+            app,
+            legacySyntheticTypes,
+            ImmutableMap.of(),
+            ImmutableList.of()),
+        syntheticItems.keySet());
+  }
+
+  private boolean verifyNoNestedSynthetics() {
+    for (SyntheticReference item : syntheticItems.values()) {
+      // Check that a context is never a synthetic unless it is an input, thus its own context.
+      assert item.getHolder() == item.getContextType()
+          || !syntheticItems.containsKey(item.getContextType());
+    }
+    return true;
+  }
+
+  private void handleSynthesizedClassMapping(
+      List<DexProgramClass> finalSyntheticClasses,
+      DexApplication application,
+      InternalOptions options,
+      MainDexClasses mainDexClasses) {
+    if (options.intermediate) {
+      updateSynthesizedClassMapping(application, finalSyntheticClasses);
+    }
+    updateMainDexListWithSynthesizedClassMap(application, mainDexClasses);
+    if (!options.intermediate) {
+      clearSynthesizedClassMapping(application);
+    }
+  }
+
+  private void updateSynthesizedClassMapping(
+      DexApplication application, List<DexProgramClass> finalSyntheticClasses) {
+    ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
+        ArrayListMultimap.create();
+    for (DexType type : legacySyntheticTypes) {
+      DexProgramClass clazz = DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
+      if (clazz != null) {
+        for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
+          originalToSynthesized.put(origin, clazz);
+        }
+      }
+    }
+    for (DexProgramClass clazz : finalSyntheticClasses) {
+      for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
+        originalToSynthesized.put(origin, clazz);
+      }
+    }
+    for (Map.Entry<DexProgramClass, Collection<DexProgramClass>> entry :
+        originalToSynthesized.asMap().entrySet()) {
+      DexProgramClass original = entry.getKey();
+      // Use a tree set to make sure that we have an ordering on the types.
+      // These types are put in an array in annotations in the output and we
+      // need a consistent ordering on them.
+      TreeSet<DexType> synthesized = new TreeSet<>(DexType::slowCompareTo);
+      entry.getValue().stream()
+          .map(dexProgramClass -> dexProgramClass.type)
+          .forEach(synthesized::add);
+      synthesized.addAll(
+          DexAnnotation.readAnnotationSynthesizedClassMap(original, application.dexItemFactory));
+
+      DexAnnotation updatedAnnotation =
+          DexAnnotation.createAnnotationSynthesizedClassMap(
+              synthesized, application.dexItemFactory);
+
+      original.setAnnotations(original.annotations().getWithAddedOrReplaced(updatedAnnotation));
+    }
+  }
+
+  private void updateMainDexListWithSynthesizedClassMap(
+      DexApplication application, MainDexClasses mainDexClasses) {
+    if (mainDexClasses.isEmpty()) {
+      return;
+    }
+    List<DexProgramClass> newMainDexClasses = new ArrayList<>();
+    mainDexClasses.forEach(
+        dexType -> {
+          DexProgramClass programClass =
+              DexProgramClass.asProgramClassOrNull(application.definitionFor(dexType));
+          if (programClass != null) {
+            Collection<DexType> derived =
+                DexAnnotation.readAnnotationSynthesizedClassMap(
+                    programClass, application.dexItemFactory);
+            for (DexType type : derived) {
+              DexProgramClass syntheticClass =
+                  DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
+              if (syntheticClass != null) {
+                newMainDexClasses.add(syntheticClass);
+              }
+            }
+          }
+        });
+    mainDexClasses.addAll(newMainDexClasses);
+  }
+
+  private void clearSynthesizedClassMapping(DexApplication application) {
+    for (DexProgramClass clazz : application.classes()) {
+      clazz.setAnnotations(
+          clazz.annotations().getWithout(application.dexItemFactory.annotationSynthesizedClassMap));
+    }
+  }
+
+  private static void buildLensAndProgram(
+      DexApplication app,
+      Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
+      Predicate<DexType> isSyntheticType,
+      MainDexClasses mainDexClasses,
+      Builder lensBuilder,
+      DexItemFactory factory,
+      List<DexProgramClass> normalClasses,
+      List<DexProgramClass> newSyntheticClasses) {
+
+    for (DexProgramClass clazz : app.classes()) {
+      if (!isSyntheticType.test(clazz.type)) {
+        normalClasses.add(clazz);
+      }
+    }
+
+    syntheticMethodGroups.forEach(
+        (syntheticType, syntheticGroup) -> {
+          SyntheticMethodDefinition firstMember = syntheticGroup.getRepresentative();
+          SynthesizingContext context = firstMember.getContext();
+          SyntheticClassBuilder builder =
+              new SyntheticClassBuilder(syntheticType, context, factory);
+          // TODO(b/158159959): Support grouping multiple methods per synthetic class.
+          builder.addMethod(
+              methodBuilder -> {
+                DexEncodedMethod definition = firstMember.getMethod().getDefinition();
+                methodBuilder
+                    .setAccessFlags(definition.accessFlags)
+                    .setProto(definition.getProto())
+                    .setCode(m -> definition.getCode());
+              });
+          DexProgramClass externalSyntheticClass = builder.build();
+          assert externalSyntheticClass.getMethodCollection().size() == 1;
+          DexEncodedMethod externalSyntheticMethod =
+              externalSyntheticClass.methods().iterator().next();
+          newSyntheticClasses.add(externalSyntheticClass);
+          for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
+            lensBuilder.map(
+                member.getMethod().getHolder().getType(), externalSyntheticClass.getType());
+            lensBuilder.map(member.getMethod().getReference(), externalSyntheticMethod.method);
+            DexType memberContext = member.getContextType();
+            DexProgramClass from =
+                DexProgramClass.asProgramClassOrNull(app.definitionFor(memberContext));
+            if (from != null) {
+              externalSyntheticClass.addSynthesizedFrom(from);
+              if (mainDexClasses.contains(from)) {
+                mainDexClasses.add(externalSyntheticClass);
+              }
+            }
+          }
+        });
+  }
+
+  private static <T extends SyntheticDefinition & Comparable<T>>
+      Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
+          Map<HashCode, List<T>> potentialEquivalences, DexItemFactory factory) {
+    Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
+    potentialEquivalences.forEach(
+        (hash, members) -> {
+          // Get a representative member and add to its group.
+          T representative = findDeterministicRepresentative(members);
+          List<T> group = new ArrayList<>(members.size());
+          group.add(representative);
+          // Each other member is in the shared group if it is actually equal to the first member.
+          for (T member : members) {
+            if (member != representative) {
+              if (member.isEquivalentTo(representative)) {
+                group.add(member);
+              } else {
+                // The member becomes a new singleton group.
+                // TODO(b/158159959): Consider checking for sub-groups of matching members.
+                groupsPerContext
+                    .computeIfAbsent(member.getContextType(), k -> new ArrayList<>())
+                    .add(new EquivalenceGroup<>(member));
+              }
+            }
+          }
+          groupsPerContext
+              .computeIfAbsent(representative.getContextType(), k -> new ArrayList<>())
+              .add(new EquivalenceGroup<>(representative, group));
+        });
+    Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
+    groupsPerContext.forEach(
+        (context, groups) -> {
+          groups.sort(EquivalenceGroup::compareTo);
+          for (int i = 0; i < groups.size(); i++) {
+            equivalences.put(createExternalType(context, i, factory), groups.get(i));
+          }
+        });
+    return equivalences;
+  }
+
+  private static <T extends SyntheticDefinition & Comparable<T>> T findDeterministicRepresentative(
+      List<T> members) {
+    // Pick a deterministic member as representative.
+    T smallest = members.get(0);
+    for (int i = 1; i < members.size(); i++) {
+      T next = members.get(i);
+      if (next.compareTo(smallest) < 0) {
+        smallest = next;
+      }
+    }
+    return smallest;
+  }
+
+  private static DexType createExternalType(
+      DexType representativeContext, int nextContextId, DexItemFactory factory) {
+    return factory.createType(
+        DescriptorUtils.getDescriptorFromClassBinaryName(
+            representativeContext.getInternalName()
+                + SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR
+                + nextContextId));
+  }
+
+  private static <T extends SyntheticDefinition>
+      Map<HashCode, List<T>> computePotentialEquivalences(List<T> definitions) {
+    Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
+    for (T definition : definitions) {
+      HashCode hash = definition.computeHash();
+      equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
+    }
+    return equivalences;
+  }
+
+  private List<SyntheticMethodDefinition> lookupSyntheticMethodDefinitions(
+      DexApplication finalApp) {
+    List<SyntheticMethodDefinition> methods = new ArrayList<>(syntheticItems.size());
+    for (SyntheticReference reference : syntheticItems.values()) {
+      SyntheticDefinition definition = reference.lookupDefinition(finalApp::definitionFor);
+      assert definition != null;
+      assert definition instanceof SyntheticMethodDefinition;
+      if (definition != null && definition instanceof SyntheticMethodDefinition) {
+        methods.add(((SyntheticMethodDefinition) definition));
+      }
+    }
+    return methods;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
new file mode 100644
index 0000000..b3c179f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -0,0 +1,360 @@
+// 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.synthesis;
+
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class SyntheticItems implements SyntheticDefinitionsProvider {
+
+  static final int INVALID_ID_AFTER_SYNTHETIC_FINALIZATION = -1;
+
+  /**
+   * The internal synthetic class separator is only used for representing synthetic items during
+   * compilation. In particular, this separator must never be used to write synthetic classes to the
+   * final compilation result.
+   */
+  public static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic";
+
+  /**
+   * The external synthetic class separator is used when writing classes. It may appear in types
+   * during compilation as the output of a compilation may be the input to another.
+   */
+  public static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic";
+
+  /** Method prefix when generating synthetic methods in a class. */
+  static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
+
+  public static boolean verifyNotInternalSynthetic(DexType type) {
+    assert !type.toDescriptorString().contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+    return true;
+  }
+
+  /** Globally incremented id for the next internal synthetic class. */
+  private int nextSyntheticId;
+
+  /**
+   * Thread safe collection of synthesized classes that are not yet committed to the application.
+   * TODO(b/158159959): Remove legacy support.
+   */
+  private final Map<DexType, DexProgramClass> legacyPendingClasses = new ConcurrentHashMap<>();
+
+  /**
+   * Immutable set of synthetic types in the application (eg, committed). TODO(b/158159959): Remove
+   * legacy support.
+   */
+  private final ImmutableSet<DexType> legacySyntheticTypes;
+
+  /** Thread safe collection of synthetic items not yet committed to the application. */
+  private final ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions =
+      new ConcurrentHashMap<>();
+
+  /** Mapping from synthetic type to its synthetic description. */
+  private final ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems;
+
+  // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
+  public static SyntheticItems createInitialSyntheticItems() {
+    return new SyntheticItems(0, ImmutableSet.of(), ImmutableMap.of());
+  }
+
+  // Only for conversion to a mutable synthetic items collection.
+  SyntheticItems(CommittedItems commit) {
+    this(commit.nextSyntheticId, commit.legacySyntheticTypes, commit.syntheticItems);
+  }
+
+  private SyntheticItems(
+      int nextSyntheticId,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems) {
+    this.nextSyntheticId = nextSyntheticId;
+    this.legacySyntheticTypes = legacySyntheticTypes;
+    this.nonLecacySyntheticItems = nonLecacySyntheticItems;
+    assert nonLecacySyntheticItems.keySet().stream()
+        .noneMatch(
+            t -> t.toDescriptorString().endsWith(getSyntheticDescriptorSuffix(nextSyntheticId)));
+    assert Sets.intersection(nonLecacySyntheticItems.keySet(), legacySyntheticTypes).isEmpty();
+  }
+
+  // Internal synthetic id creation helpers.
+
+  private synchronized int getNextSyntheticId() {
+    if (nextSyntheticId == INVALID_ID_AFTER_SYNTHETIC_FINALIZATION) {
+      throw new InternalCompilerError(
+          "Unexpected attempt to synthesize classes after synthetic finalization.");
+    }
+    return nextSyntheticId++;
+  }
+
+  private static DexType hygienicType(
+      DexItemFactory factory, int syntheticId, SynthesizingContext context) {
+    String contextDesc = context.type.toDescriptorString();
+    String prefix = contextDesc.substring(0, contextDesc.length() - 1);
+    String syntheticDesc = prefix + getSyntheticDescriptorSuffix(syntheticId);
+    return factory.createType(syntheticDesc);
+  }
+
+  private static String getSyntheticDescriptorSuffix(int syntheticId) {
+    return INTERNAL_SYNTHETIC_CLASS_SEPARATOR + syntheticId + ";";
+  }
+
+  // Predicates and accessors.
+
+  @Override
+  public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
+    DexProgramClass pending = legacyPendingClasses.get(type);
+    if (pending == null) {
+      SyntheticDefinition item = pendingDefinitions.get(type);
+      if (item != null) {
+        pending = item.getHolder();
+      }
+    }
+    if (pending != null) {
+      assert baseDefinitionFor.apply(type) == null
+          : "Pending synthetic definition also present in the active program: " + type;
+      return pending;
+    }
+    return baseDefinitionFor.apply(type);
+  }
+
+  public boolean hasPendingSyntheticClasses() {
+    return !legacyPendingClasses.isEmpty() || !pendingDefinitions.isEmpty();
+  }
+
+  public Collection<DexProgramClass> getPendingSyntheticClasses() {
+    List<DexProgramClass> pending =
+        new ArrayList<>(pendingDefinitions.size() + legacyPendingClasses.size());
+    for (SyntheticDefinition item : pendingDefinitions.values()) {
+      pending.add(item.getHolder());
+    }
+    pending.addAll(legacyPendingClasses.values());
+    return Collections.unmodifiableList(pending);
+  }
+
+  private boolean isCommittedSynthetic(DexType type) {
+    return nonLecacySyntheticItems.containsKey(type) || legacySyntheticTypes.contains(type);
+  }
+
+  public boolean isPendingSynthetic(DexType type) {
+    return pendingDefinitions.containsKey(type) || legacyPendingClasses.containsKey(type);
+  }
+
+  public boolean isSyntheticClass(DexType type) {
+    return isCommittedSynthetic(type)
+        || isPendingSynthetic(type)
+        // TODO(b/158159959): Remove usage of name-based identification.
+        || type.isD8R8SynthesizedClassType();
+  }
+
+  public boolean isSyntheticClass(DexProgramClass clazz) {
+    return isSyntheticClass(clazz.type);
+  }
+
+  public Collection<DexProgramClass> getLegacyPendingClasses() {
+    return Collections.unmodifiableCollection(legacyPendingClasses.values());
+  }
+
+  private SynthesizingContext getSynthesizingContext(DexProgramClass context) {
+    SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.type);
+    if (pendingItemContext != null) {
+      return pendingItemContext.getContext();
+    }
+    SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.type);
+    return committedItemContext != null
+        ? committedItemContext.getContext()
+        : new SynthesizingContext(context.type, context.origin);
+  }
+
+  // Addition and creation of synthetic items.
+
+  // TODO(b/158159959): Remove the usage of this direct class addition (and name-based id).
+  public void addLegacySyntheticClass(DexProgramClass clazz) {
+    assert clazz.type.isD8R8SynthesizedClassType();
+    assert !isCommittedSynthetic(clazz.type);
+    DexProgramClass previous = legacyPendingClasses.put(clazz.type, clazz);
+    assert previous == null || previous == clazz;
+  }
+
+  /** Create a single synthetic method item. */
+  public ProgramMethod createMethod(
+      DexProgramClass context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // The is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = getSynthesizingContext(context);
+    DexType type = hygienicType(factory, getNextSyntheticId(), outerContext);
+    DexProgramClass clazz =
+        new SyntheticClassBuilder(type, outerContext, factory).addMethod(fn).build();
+    ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
+    addPendingDefinition(new SyntheticMethodDefinition(outerContext, method));
+    return method;
+  }
+
+  private void addPendingDefinition(SyntheticDefinition definition) {
+    pendingDefinitions.put(definition.getHolder().getType(), definition);
+  }
+
+  // Commit of the synthetic items to a new fully populated application.
+
+  public CommittedItems commit(DexApplication application) {
+    return commitPrunedClasses(application, Collections.emptySet());
+  }
+
+  public CommittedItems commitPrunedClasses(
+      DexApplication application, Set<DexType> removedClasses) {
+    return commit(
+        application,
+        removedClasses,
+        legacyPendingClasses,
+        legacySyntheticTypes,
+        pendingDefinitions,
+        nonLecacySyntheticItems,
+        nextSyntheticId);
+  }
+
+  public CommittedItems commitRewrittenWithLens(DexApplication application, NestedGraphLens lens) {
+    // Rewrite the previously committed synthetic types.
+    ImmutableSet<DexType> rewrittenLegacyTypes = lens.rewriteTypes(this.legacySyntheticTypes);
+    ImmutableMap.Builder<DexType, SyntheticReference> rewrittenItems = ImmutableMap.builder();
+    for (SyntheticReference reference : nonLecacySyntheticItems.values()) {
+      SyntheticReference rewritten = reference.rewrite(lens);
+      rewrittenItems.put(rewritten.getHolder(), rewritten);
+    }
+    // No pending item should need rewriting.
+    assert legacyPendingClasses.keySet().equals(lens.rewriteTypes(legacyPendingClasses.keySet()));
+    assert pendingDefinitions.keySet().equals(lens.rewriteTypes(pendingDefinitions.keySet()));
+    return commit(
+        application,
+        Collections.emptySet(),
+        legacyPendingClasses,
+        rewrittenLegacyTypes,
+        pendingDefinitions,
+        rewrittenItems.build(),
+        nextSyntheticId);
+  }
+
+  private static CommittedItems commit(
+      DexApplication application,
+      Set<DexType> removedClasses,
+      Map<DexType, DexProgramClass> legacyPendingClasses,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions,
+      ImmutableMap<DexType, SyntheticReference> syntheticItems,
+      int nextSyntheticId) {
+    // Legacy synthetics must already have been committed.
+    assert verifyClassesAreInApp(application, legacyPendingClasses.values());
+    // Add the set of legacy definitions to the synthetic types.
+    ImmutableSet<DexType> mergedLegacyTypes = legacySyntheticTypes;
+    if (!legacyPendingClasses.isEmpty() || !removedClasses.isEmpty()) {
+      ImmutableSet.Builder<DexType> legacyBuilder = ImmutableSet.builder();
+      filteredAdd(legacySyntheticTypes, removedClasses, legacyBuilder);
+      filteredAdd(legacyPendingClasses.keySet(), removedClasses, legacyBuilder);
+      mergedLegacyTypes = legacyBuilder.build();
+    }
+    // The set of synthetic items is the union of the previous types plus the pending additions.
+    ImmutableMap<DexType, SyntheticReference> mergedItems;
+    ImmutableList<DexType> additions;
+    DexApplication amendedApplication;
+    if (pendingDefinitions.isEmpty()) {
+      mergedItems = filteredCopy(syntheticItems, removedClasses);
+      additions = ImmutableList.of();
+      amendedApplication = application;
+    } else {
+      DexApplication.Builder<?> appBuilder = application.builder();
+      ImmutableMap.Builder<DexType, SyntheticReference> itemsBuilder = ImmutableMap.builder();
+      ImmutableList.Builder<DexType> additionsBuilder = ImmutableList.builder();
+      for (SyntheticDefinition definition : pendingDefinitions.values()) {
+        if (removedClasses.contains(definition.getHolder().getType())) {
+          continue;
+        }
+        SyntheticReference reference = definition.toReference();
+        itemsBuilder.put(reference.getHolder(), reference);
+        additionsBuilder.add(definition.getHolder().getType());
+        appBuilder.addProgramClass(definition.getHolder());
+      }
+      filteredAdd(syntheticItems, removedClasses, itemsBuilder);
+      mergedItems = itemsBuilder.build();
+      additions = additionsBuilder.build();
+      amendedApplication = appBuilder.build();
+    }
+    return new CommittedItems(
+        nextSyntheticId, amendedApplication, mergedLegacyTypes, mergedItems, additions);
+  }
+
+  private static void filteredAdd(
+      Set<DexType> input, Set<DexType> excludeSet, Builder<DexType> result) {
+    if (excludeSet.isEmpty()) {
+      result.addAll(input);
+    } else {
+      for (DexType type : input) {
+        if (!excludeSet.contains(type)) {
+          result.add(type);
+        }
+      }
+    }
+  }
+
+  private static ImmutableMap<DexType, SyntheticReference> filteredCopy(
+      ImmutableMap<DexType, SyntheticReference> syntheticItems, Set<DexType> removedClasses) {
+    if (removedClasses.isEmpty()) {
+      return syntheticItems;
+    }
+    ImmutableMap.Builder<DexType, SyntheticReference> builder = ImmutableMap.builder();
+    filteredAdd(syntheticItems, removedClasses, builder);
+    return builder.build();
+  }
+
+  private static void filteredAdd(
+      ImmutableMap<DexType, SyntheticReference> syntheticItems,
+      Set<DexType> removedClasses,
+      ImmutableMap.Builder<DexType, SyntheticReference> builder) {
+    if (removedClasses.isEmpty()) {
+      builder.putAll(syntheticItems);
+    } else {
+      syntheticItems.forEach(
+          (t, r) -> {
+            if (!removedClasses.contains(t)) {
+              builder.put(t, r);
+            }
+          });
+    }
+  }
+
+  private static boolean verifyClassesAreInApp(
+      DexApplication app, Collection<DexProgramClass> classes) {
+    for (DexProgramClass clazz : classes) {
+      assert app.programDefinitionFor(clazz.type) != null : "Missing synthetic: " + clazz.type;
+    }
+    return true;
+  }
+
+  // Finalization of synthetic items.
+
+  public Result computeFinalSynthetics(AppView<?> appView) {
+    assert !hasPendingSyntheticClasses();
+    return new SyntheticFinalization(
+            appView.options(), legacySyntheticTypes, nonLecacySyntheticItems)
+        .computeFinalSynthetics(appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
new file mode 100644
index 0000000..90e593c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -0,0 +1,77 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+
+public class SyntheticMethodBuilder {
+
+  public interface SyntheticCodeGenerator {
+    Code generate(DexMethod method);
+  }
+
+  private final SyntheticClassBuilder parent;
+  private final String name;
+  private DexProto proto = null;
+  private SyntheticCodeGenerator codeGenerator = null;
+  private MethodAccessFlags accessFlags = null;
+
+  SyntheticMethodBuilder(SyntheticClassBuilder parent, String name) {
+    this.parent = parent;
+    this.name = name;
+  }
+
+  public SyntheticMethodBuilder setProto(DexProto proto) {
+    this.proto = proto;
+    return this;
+  }
+
+  public SyntheticMethodBuilder setCode(SyntheticCodeGenerator codeGenerator) {
+    this.codeGenerator = codeGenerator;
+    return this;
+  }
+
+  public SyntheticMethodBuilder setAccessFlags(MethodAccessFlags accessFlags) {
+    this.accessFlags = accessFlags;
+    return this;
+  }
+
+  DexEncodedMethod build() {
+    boolean isCompilerSynthesized = true;
+    DexMethod methodSignature = getMethodSignature();
+    return new DexEncodedMethod(
+        methodSignature,
+        getAccessFlags(),
+        getAnnotations(),
+        getParameterAnnotations(),
+        getCodeObject(methodSignature),
+        isCompilerSynthesized);
+  }
+
+  private DexMethod getMethodSignature() {
+    return parent.getFactory().createMethod(parent.getType(), proto, name);
+  }
+
+  private MethodAccessFlags getAccessFlags() {
+    return accessFlags;
+  }
+
+  private DexAnnotationSet getAnnotations() {
+    return DexAnnotationSet.empty();
+  }
+
+  private ParameterAnnotationsList getParameterAnnotations() {
+    return ParameterAnnotationsList.empty();
+  }
+
+  private Code getCodeObject(DexMethod methodSignature) {
+    return codeGenerator.generate(methodSignature);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
new file mode 100644
index 0000000..6fb07c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -0,0 +1,67 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import java.util.Comparator;
+
+/**
+ * Definition of a synthetic method item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticMethodDefinition extends SyntheticDefinition
+    implements Comparable<SyntheticMethodDefinition> {
+
+  private final ProgramMethod method;
+
+  SyntheticMethodDefinition(SynthesizingContext context, ProgramMethod method) {
+    super(context);
+    this.method = method;
+  }
+
+  public ProgramMethod getMethod() {
+    return method;
+  }
+
+  @Override
+  SyntheticReference toReference() {
+    return new SyntheticMethodReference(getContext(), method.getReference());
+  }
+
+  @Override
+  DexProgramClass getHolder() {
+    return method.getHolder();
+  }
+
+  @Override
+  HashCode computeHash() {
+    Hasher hasher = Hashing.sha256().newHasher();
+    method.getDefinition().hashSyntheticContent(hasher);
+    return hasher.hash();
+  }
+
+  @Override
+  boolean isEquivalentTo(SyntheticDefinition other) {
+    if (!(other instanceof SyntheticMethodDefinition)) {
+      return false;
+    }
+    SyntheticMethodDefinition o = (SyntheticMethodDefinition) other;
+    return method.getDefinition().isSyntheticContentEqual(o.method.getDefinition());
+  }
+
+  // Since methods are sharable they must define an order from which representatives can be found.
+  @Override
+  public int compareTo(SyntheticMethodDefinition other) {
+    return Comparator.comparing(SyntheticMethodDefinition::getContextType, DexType::slowCompareTo)
+        .thenComparing(m -> m.method.getDefinition(), DexEncodedMethod::syntheticCompareTo)
+        .compare(this, other);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
new file mode 100644
index 0000000..326eee0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -0,0 +1,50 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.function.Function;
+
+/**
+ * Reference to a synthetic method item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticMethodReference extends SyntheticReference {
+  final DexMethod method;
+
+  SyntheticMethodReference(SynthesizingContext context, DexMethod method) {
+    super(context);
+    this.method = method;
+  }
+
+  @Override
+  DexType getHolder() {
+    return method.holder;
+  }
+
+  @Override
+  SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+    DexClass clazz = definitions.apply(method.holder);
+    if (clazz == null) {
+      return null;
+    }
+    assert clazz.isProgramClass();
+    ProgramMethod definition = clazz.asProgramClass().lookupProgramMethod(this.method);
+    return new SyntheticMethodDefinition(getContext(), definition);
+  }
+
+  @Override
+  SyntheticReference rewrite(NestedGraphLens lens) {
+    SynthesizingContext context = getContext().rewrite(lens);
+    DexMethod rewritten = lens.lookupMethod(method);
+    return context == getContext() && rewritten == method
+        ? this
+        : new SyntheticMethodReference(context, method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
new file mode 100644
index 0000000..9981624
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -0,0 +1,41 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.origin.Origin;
+import java.util.function.Function;
+
+/**
+ * Base type for a reference to a synthetic item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+abstract class SyntheticReference {
+  private final SynthesizingContext context;
+
+  SyntheticReference(SynthesizingContext context) {
+    this.context = context;
+  }
+
+  abstract SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions);
+
+  final SynthesizingContext getContext() {
+    return context;
+  }
+
+  final DexType getContextType() {
+    return context.type;
+  }
+
+  final Origin getContextOrigin() {
+    return context.origin;
+  }
+
+  abstract DexType getHolder();
+
+  abstract SyntheticReference rewrite(NestedGraphLens lens);
+}
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 c23e47d..0164b4e 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -32,7 +32,10 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -472,7 +475,7 @@
               dumpFeatureSplitFileNames(options.featureSplitConfiguration),
               nextDexIndex,
               out,
-              options.featureSplitConfiguration);
+              options);
       nextDexIndex = dumpClasspathResources(nextDexIndex, out);
       nextDexIndex = dumpLibraryResources(nextDexIndex, out);
     } catch (IOException | ResourceException e) {
@@ -534,12 +537,16 @@
       Map<FeatureSplit, String> featureSplitArchiveNames,
       int nextDexIndex,
       ZipOutputStream out,
-      FeatureSplitConfiguration featureSplitConfiguration)
+      InternalOptions options)
       throws IOException, ResourceException {
     Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams =
         new IdentityHashMap<>();
     Map<FeatureSplit, ZipOutputStream> featureSplitArchiveOutputStreams = new IdentityHashMap<>();
     try {
+      DexItemFactory dexItemFactory = options.dexItemFactory();
+      FeatureSplitConfiguration featureSplitConfiguration = options.featureSplitConfiguration;
+      ClassToFeatureSplitMap classToFeatureSplitMap =
+          ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(options);
       if (featureSplitConfiguration != null) {
         for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
           ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream();
@@ -567,9 +574,8 @@
                       nextDexIndex,
                       classDescriptor -> {
                         if (featureSplitConfiguration != null) {
-                          FeatureSplit featureSplit =
-                              featureSplitConfiguration.getFeatureSplitFromClassDescriptor(
-                                  classDescriptor);
+                          DexType type = dexItemFactory.createType(classDescriptor);
+                          FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(type);
                           if (featureSplit != null) {
                             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 a9d5c3d..cf31dec 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -104,6 +104,10 @@
 
   public final DexItemFactory itemFactory;
 
+  public DexItemFactory dexItemFactory() {
+    return itemFactory;
+  }
+
   public boolean hasProguardConfiguration() {
     return proguardConfiguration != null;
   }
@@ -370,7 +374,7 @@
   }
 
   public boolean shouldBackportMethods() {
-    return !hasConsumer() || isGeneratingDex();
+    return !hasConsumer() || isGeneratingDex() || cfToCfDesugar;
   }
 
   public boolean shouldKeepStackMapTable() {
diff --git a/src/test/java/com/android/tools/r8/DesugarTestBuilder.java b/src/test/java/com/android/tools/r8/DesugarTestBuilder.java
new file mode 100644
index 0000000..33ef940
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DesugarTestBuilder.java
@@ -0,0 +1,44 @@
+// 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;
+
+import com.android.tools.r8.utils.Pair;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class DesugarTestBuilder
+    extends TestBuilderCollection<
+        DesugarTestConfiguration, DesugarTestRunResult, DesugarTestBuilder> {
+
+  public static DesugarTestBuilder create(
+      TestState state,
+      List<Pair<DesugarTestConfiguration, TestBuilder<? extends TestRunResult<?>, ?>>>
+          testBuilders) {
+    return new DesugarTestBuilder(state, testBuilders);
+  }
+
+  private DesugarTestBuilder(
+      TestState state,
+      List<Pair<DesugarTestConfiguration, TestBuilder<? extends TestRunResult<?>, ?>>> builders) {
+    super(state, builders);
+  }
+
+  @Override
+  DesugarTestBuilder self() {
+    return this;
+  }
+
+  @Override
+  public DesugarTestRunResult run(TestRuntime runtime, String mainClass, String... args)
+      throws CompilationFailedException, ExecutionException, IOException {
+    List<Pair<DesugarTestConfiguration, TestRunResult<?>>> runs = new ArrayList<>(builders.size());
+    for (Pair<DesugarTestConfiguration, TestBuilder<? extends TestRunResult<?>, ?>> builder :
+        builders) {
+      runs.add(new Pair<>(builder.getFirst(), builder.getSecond().run(runtime, mainClass, args)));
+    }
+    return DesugarTestRunResult.create(runs);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/DesugarTestConfiguration.java b/src/test/java/com/android/tools/r8/DesugarTestConfiguration.java
new file mode 100644
index 0000000..c748ad3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DesugarTestConfiguration.java
@@ -0,0 +1,32 @@
+// 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;
+
+/** The configurations for a desugar test */
+public enum DesugarTestConfiguration {
+  // Javac generated code with no desugaring (reference run).
+  JAVAC,
+  // Javac generated code with desugaring to class file.
+  D8_CF,
+  // Javac generated code with desugaring to DEX.
+  D8_DEX,
+  // Javac generated code with desugaring to class file and then compiled to DEX without desugaring.
+  D8_CF_D8_DEX;
+
+  public static boolean isJavac(DesugarTestConfiguration c) {
+    return c == JAVAC;
+  }
+
+  public static boolean isNotJavac(DesugarTestConfiguration c) {
+    return c != JAVAC;
+  }
+
+  public static boolean isNotDesugared(DesugarTestConfiguration c) {
+    return isJavac(c);
+  }
+
+  public static boolean isDesugared(DesugarTestConfiguration c) {
+    return isNotJavac(c);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/DesugarTestRunResult.java b/src/test/java/com/android/tools/r8/DesugarTestRunResult.java
new file mode 100644
index 0000000..4bffdfa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DesugarTestRunResult.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.utils.Pair;
+import java.util.List;
+
+public class DesugarTestRunResult
+    extends TestRunResultCollection<DesugarTestConfiguration, DesugarTestRunResult> {
+
+  public static DesugarTestRunResult create(
+      List<Pair<DesugarTestConfiguration, TestRunResult<?>>> runs) {
+    assert !runs.isEmpty();
+    return new DesugarTestRunResult(runs);
+  }
+
+  private DesugarTestRunResult(List<Pair<DesugarTestConfiguration, TestRunResult<?>>> runs) {
+    super(runs);
+  }
+
+  @Override
+  DesugarTestRunResult self() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/NeverMerge.java b/src/test/java/com/android/tools/r8/NoHorizontalClassMerging.java
similarity index 71%
copy from src/test/java/com/android/tools/r8/NeverMerge.java
copy to src/test/java/com/android/tools/r8/NoHorizontalClassMerging.java
index 7c6922a..8e45d4a 100644
--- a/src/test/java/com/android/tools/r8/NeverMerge.java
+++ b/src/test/java/com/android/tools/r8/NoHorizontalClassMerging.java
@@ -1,10 +1,11 @@
-// 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 com.android.tools.r8;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
 
 @Target({ElementType.TYPE})
-public @interface NeverMerge {}
+public @interface NoHorizontalClassMerging {}
diff --git a/src/test/java/com/android/tools/r8/NeverMerge.java b/src/test/java/com/android/tools/r8/NoStaticClassMerging.java
similarity index 72%
copy from src/test/java/com/android/tools/r8/NeverMerge.java
copy to src/test/java/com/android/tools/r8/NoStaticClassMerging.java
index 7c6922a..b97416f 100644
--- a/src/test/java/com/android/tools/r8/NeverMerge.java
+++ b/src/test/java/com/android/tools/r8/NoStaticClassMerging.java
@@ -1,10 +1,11 @@
-// 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 com.android.tools.r8;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
 
 @Target({ElementType.TYPE})
-public @interface NeverMerge {}
+public @interface NoStaticClassMerging {}
diff --git a/src/test/java/com/android/tools/r8/NeverMerge.java b/src/test/java/com/android/tools/r8/NoVerticalClassMerging.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/NeverMerge.java
rename to src/test/java/com/android/tools/r8/NoVerticalClassMerging.java
index 7c6922a..33d15ce 100644
--- a/src/test/java/com/android/tools/r8/NeverMerge.java
+++ b/src/test/java/com/android/tools/r8/NoVerticalClassMerging.java
@@ -7,4 +7,4 @@
 import java.lang.annotation.Target;
 
 @Target({ElementType.TYPE})
-public @interface NeverMerge {}
+public @interface NoVerticalClassMerging {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 3c8a16b..85705d2 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import static com.android.tools.r8.dexsplitter.SplitterTestBase.simpleSplitProvider;
+import static com.android.tools.r8.dexsplitter.SplitterTestBase.splitWithNonJavaFile;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.R8Command.Builder;
@@ -15,12 +16,16 @@
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
+import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
+import com.android.tools.r8.shaking.NoStaticClassMergingRule;
+import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Pair;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -55,7 +60,9 @@
   private boolean enableConstantArgumentAnnotations = false;
   private boolean enableInliningAnnotations = false;
   private boolean enableMemberValuePropagationAnnotations = false;
-  private boolean enableMergeAnnotations = false;
+  private boolean enableNoVerticalClassMergingAnnotations = false;
+  private boolean enableNoHorizontalClassMergingAnnotations = false;
+  private boolean enableNoStaticClassMergingAnnotations = false;
   private boolean enableNeverClassInliningAnnotations = false;
   private boolean enableNeverReprocessClassInitializerAnnotations = false;
   private boolean enableNeverReprocessMethodAnnotations = false;
@@ -76,7 +83,9 @@
     if (enableConstantArgumentAnnotations
         || enableInliningAnnotations
         || enableMemberValuePropagationAnnotations
-        || enableMergeAnnotations
+        || enableNoVerticalClassMergingAnnotations
+        || enableNoHorizontalClassMergingAnnotations
+        || enableNoStaticClassMergingAnnotations
         || enableNeverClassInliningAnnotations
         || enableNeverReprocessClassInitializerAnnotations
         || enableNeverReprocessMethodAnnotations
@@ -406,10 +415,38 @@
     return self();
   }
 
-  public T enableMergeAnnotations() {
-    if (!enableMergeAnnotations) {
-      enableMergeAnnotations = true;
-      addInternalKeepRules("-nevermerge @com.android.tools.r8.NeverMerge class *");
+  private void addInternalMatchInterfaceRule(String name, Class matchInterface) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("-");
+    sb.append(name);
+    sb.append(" @");
+    sb.append(matchInterface.getTypeName());
+    sb.append(" class *");
+    addInternalKeepRules(sb.toString());
+  }
+
+  public T enableNoVerticalClassMergingAnnotations() {
+    if (!enableNoVerticalClassMergingAnnotations) {
+      enableNoVerticalClassMergingAnnotations = true;
+      addInternalMatchInterfaceRule(
+          NoVerticalClassMergingRule.RULE_NAME, NoVerticalClassMerging.class);
+    }
+    return self();
+  }
+
+  public T enableNoHorizontalClassMergingAnnotations() {
+    if (!enableNoHorizontalClassMergingAnnotations) {
+      enableNoHorizontalClassMergingAnnotations = true;
+      addInternalMatchInterfaceRule(
+          NoHorizontalClassMergingRule.RULE_NAME, NoHorizontalClassMerging.class);
+    }
+    return self();
+  }
+
+  public T enableNoStaticClassMergingAnnotations() {
+    if (!enableNoStaticClassMergingAnnotations) {
+      enableNoStaticClassMergingAnnotations = true;
+      addInternalMatchInterfaceRule(NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
     }
     return self();
   }
@@ -593,4 +630,14 @@
     features.add(path);
     return self();
   }
+
+  public T addFeatureSplitWithResources(
+      Collection<Pair<String, String>> nonJavaFiles, Class<?>... classes) throws IOException {
+    Path path = getState().getNewTempFolder().resolve("feature.zip");
+    builder.addFeatureSplit(
+        builder ->
+            splitWithNonJavaFile(builder, path, getState().getTempFolder(), nonJavaFiles, classes));
+    features.add(path);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 3fa1091..c2a1dac 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -71,6 +71,10 @@
     return features.get(index);
   }
 
+  public List<Path> getFeatures() {
+    return features;
+  }
+
   @Override
   public String getStdout() {
     return state.getStdout();
@@ -132,6 +136,10 @@
     return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector);
   }
 
+  public R8TestCompileResult addFeatureSplitsToRunClasspathFiles() {
+    return addRunClasspathFiles(features);
+  }
+
   public R8TestRunResult runFeature(TestRuntime runtime, Class<?> mainFeatureClass)
       throws IOException {
     return runFeature(runtime, mainFeatureClass, features.get(0));
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
deleted file mode 100644
index a022ecf..0000000
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2016, 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;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class R8UnreachableCodeTest {
-
-  private static final Path SMALI_DIR = Paths.get(ToolHelper.SMALI_BUILD_DIR);
-
-  @Ignore
-  @Test
-  public void UnreachableCode() throws IOException, ExecutionException {
-    String name = "unreachable-code-1";
-    AndroidApp input =
-        AndroidApp.builder()
-            .addProgramFiles(SMALI_DIR.resolve(name).resolve(name + ".dex"))
-            .build();
-    ExecutorService executorService = Executors.newSingleThreadExecutor();
-    Timing timing = Timing.empty();
-    InternalOptions options = new InternalOptions();
-    options.programConsumer = DexIndexedConsumer.emptyConsumer();
-    DirectMappedDexApplication application =
-        new ApplicationReader(input, options, timing).read(executorService).toDirect();
-    IRConverter converter = new IRConverter(AppView.createForR8(application), null);
-    converter.optimize();
-    DexProgramClass clazz = application.classes().iterator().next();
-    assertEquals(4, clazz.getMethodCollection().numberOfDirectMethods());
-    for (DexEncodedMethod method : clazz.directMethods()) {
-      if (!method.method.name.toString().equals("main")) {
-        assertEquals(2, method.getCode().asDexCode().instructions.length);
-      }
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 342920c..d065497 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
@@ -45,6 +46,8 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.NoStaticClassMergingRule;
+import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.shaking.ProguardClassNameList;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -76,6 +79,7 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.base.Predicates;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.collect.ImmutableList;
@@ -105,6 +109,7 @@
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.jar.JarOutputStream;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -220,46 +225,78 @@
     return testForRuntime(parameters.getRuntime(), parameters.getApiLevel());
   }
 
-  public TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(TestParameters parameters) {
+  public TestBuilder<DesugarTestRunResult, ?> testForDesugaring(TestParameters parameters) {
     return testForDesugaring(
-        parameters.getRuntime().getBackend(), parameters.getApiLevel(), o -> {});
+        parameters.getRuntime().getBackend(),
+        parameters.getApiLevel(),
+        o -> {},
+        Predicates.alwaysTrue());
   }
 
-  public TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(
+  public TestBuilder<DesugarTestRunResult, ?> testForDesugaring(
       TestParameters parameters, Consumer<InternalOptions> optionsModification) {
     return testForDesugaring(
-        parameters.getRuntime().getBackend(), parameters.getApiLevel(), optionsModification);
+        parameters.getRuntime().getBackend(),
+        parameters.getApiLevel(),
+        optionsModification,
+        Predicates.alwaysTrue());
   }
 
-  private TestBuilder<? extends TestRunResult<?>, ?> testForDesugaring(
-      Backend backend, AndroidApiLevel apiLevel, Consumer<InternalOptions> optionsModification) {
+  @Deprecated
+  // This is not supposed to be used for tests. It is here for debugging where filtering to run
+  // only some (typically one) test configuration is helpful.
+  public TestBuilder<DesugarTestRunResult, ?> testForDesugaring(
+      TestParameters parameters,
+      Consumer<InternalOptions> optionsModification,
+      Predicate<DesugarTestConfiguration> filter) {
+    return testForDesugaring(
+        parameters.getRuntime().getBackend(),
+        parameters.getApiLevel(),
+        optionsModification,
+        filter);
+  }
+
+  private TestBuilder<DesugarTestRunResult, ?> testForDesugaring(
+      Backend backend,
+      AndroidApiLevel apiLevel,
+      Consumer<InternalOptions> optionsModification,
+      Predicate<DesugarTestConfiguration> filter) {
     assert apiLevel != null : "No API level. Add .withAllApiLevelsAlsoForCf() to test parameters?";
     TestState state = new TestState(temp);
-    List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> builders;
+    ImmutableList.Builder<
+            Pair<DesugarTestConfiguration, TestBuilder<? extends TestRunResult<?>, ?>>>
+        builders = ImmutableList.builder();
     if (backend == Backend.CF) {
-      builders =
-          ImmutableList.of(
-              new Pair<>("JAVAC", JvmTestBuilder.create(state)),
-              new Pair<>(
-                  "D8/CF",
-                  D8TestBuilder.create(state, Backend.CF)
-                      .setMinApi(apiLevel)
-                      .addOptionsModification(optionsModification)));
+      if (filter.test(DesugarTestConfiguration.JAVAC)) {
+        builders.add(new Pair<>(DesugarTestConfiguration.JAVAC, JvmTestBuilder.create(state)));
+      }
+      if (filter.test(DesugarTestConfiguration.D8_CF)) {
+        builders.add(
+            new Pair<>(
+                DesugarTestConfiguration.D8_CF,
+                D8TestBuilder.create(state, Backend.CF)
+                    .setMinApi(apiLevel)
+                    .addOptionsModification(optionsModification)));
+      }
     } else {
       assert backend == Backend.DEX;
-      builders =
-          ImmutableList.of(
-              new Pair<>(
-                  "D8/DEX",
-                  D8TestBuilder.create(state, Backend.DEX)
-                      .setMinApi(apiLevel)
-                      .addOptionsModification(optionsModification)),
-              new Pair<>(
-                  "D8/DEX o D8/CF",
-                  IntermediateCfD8TestBuilder.create(state, apiLevel)
-                      .addOptionsModification(optionsModification)));
+      if (filter.test(DesugarTestConfiguration.D8_DEX)) {
+        builders.add(
+            new Pair<>(
+                DesugarTestConfiguration.D8_DEX,
+                D8TestBuilder.create(state, Backend.DEX)
+                    .setMinApi(apiLevel)
+                    .addOptionsModification(optionsModification)));
+      }
+      if (filter.test(DesugarTestConfiguration.D8_CF_D8_DEX)) {
+        builders.add(
+            new Pair<>(
+                DesugarTestConfiguration.D8_CF_D8_DEX,
+                IntermediateCfD8TestBuilder.create(state, apiLevel)
+                    .addOptionsModification(optionsModification)));
+      }
     }
-    return TestBuilderCollection.create(state, builders);
+    return DesugarTestBuilder.create(state, builders.build());
   }
 
   public ProguardTestBuilder testForProguard() {
@@ -652,6 +689,7 @@
       throws Exception {
     return AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
         readApplicationForDexOutput(app, new InternalOptions()),
+        ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
         MainDexClasses.createEmptyMainDexClasses());
   }
 
@@ -1053,8 +1091,19 @@
         + keepMainProguardConfiguration(clazz);
   }
 
-  public static String neverMergeRule() {
-    return "-nevermerge @com.android.tools.r8.NeverMerge class *";
+  @Deprecated
+  private static String matchInterfaceRule(String name, Class matchInterface) {
+    return "-" + name + " @" + matchInterface.getTypeName() + " class *";
+  }
+
+  @Deprecated
+  public static String noVerticalClassMergingRule() {
+    return matchInterfaceRule(NoVerticalClassMergingRule.RULE_NAME, NoVerticalClassMerging.class);
+  }
+
+  @Deprecated
+  public static String noStaticClassMergingRule() {
+    return matchInterfaceRule(NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
   }
 
   /**
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 73a1605..8534d9f 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -174,7 +174,9 @@
         KeepUnusedArguments.class,
         NeverClassInline.class,
         NeverInline.class,
-        NeverMerge.class,
+        NoVerticalClassMerging.class,
+        NoHorizontalClassMerging.class,
+        NoStaticClassMerging.class,
         NeverPropagateValue.class);
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestBuilderCollection.java b/src/test/java/com/android/tools/r8/TestBuilderCollection.java
index a537aea..0192832 100644
--- a/src/test/java/com/android/tools/r8/TestBuilderCollection.java
+++ b/src/test/java/com/android/tools/r8/TestBuilderCollection.java
@@ -6,49 +6,28 @@
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.Pair;
-import java.io.IOException;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
 /** Abstraction to allow setup and execution of multiple test builders. */
-public class TestBuilderCollection
-    extends TestBuilder<TestRunResultCollection, TestBuilderCollection> {
+public abstract class TestBuilderCollection<
+        C extends Enum<C>,
+        RR extends TestRunResultCollection<C, RR>,
+        T extends TestBuilderCollection<C, RR, T>>
+    extends TestBuilder<RR, T> {
 
-  public static TestBuilderCollection create(
-      TestState state,
-      List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> testBuilders) {
-    return new TestBuilderCollection(state, testBuilders);
-  }
+  final List<Pair<C, TestBuilder<? extends TestRunResult<?>, ?>>> builders;
 
-  private final List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> builders;
-
-  private TestBuilderCollection(
-      TestState state, List<Pair<String, TestBuilder<? extends TestRunResult<?>, ?>>> builders) {
+  TestBuilderCollection(
+      TestState state, List<Pair<C, TestBuilder<? extends TestRunResult<?>, ?>>> builders) {
     super(state);
     assert !builders.isEmpty();
     this.builders = builders;
   }
 
-  @Override
-  TestBuilderCollection self() {
-    return this;
-  }
-
-  @Override
-  public TestRunResultCollection run(TestRuntime runtime, String mainClass, String... args)
-      throws CompilationFailedException, ExecutionException, IOException {
-    List<Pair<String, TestRunResult<?>>> runs = new ArrayList<>(builders.size());
-    for (Pair<String, TestBuilder<? extends TestRunResult<?>, ?>> builder : builders) {
-      runs.add(new Pair<>(builder.getFirst(), builder.getSecond().run(runtime, mainClass, args)));
-    }
-    return TestRunResultCollection.create(runs);
-  }
-
-  private TestBuilderCollection forEach(Consumer<TestBuilder<? extends TestRunResult<?>, ?>> fn) {
+  private T forEach(Consumer<TestBuilder<? extends TestRunResult<?>, ?>> fn) {
     builders.forEach(b -> fn.accept(b.getSecond()));
     return self();
   }
@@ -59,42 +38,42 @@
   }
 
   @Override
-  public TestBuilderCollection addProgramFiles(Collection<Path> files) {
+  public T addProgramFiles(Collection<Path> files) {
     return forEach(b -> b.addProgramFiles(files));
   }
 
   @Override
-  public TestBuilderCollection addProgramClassFileData(Collection<byte[]> classes) {
+  public T addProgramClassFileData(Collection<byte[]> classes) {
     return forEach(b -> b.addProgramClassFileData(classes));
   }
 
   @Override
-  public TestBuilderCollection addProgramDexFileData(Collection<byte[]> data) {
+  public T addProgramDexFileData(Collection<byte[]> data) {
     return forEach(b -> b.addProgramDexFileData(data));
   }
 
   @Override
-  public TestBuilderCollection addLibraryFiles(Collection<Path> files) {
+  public T addLibraryFiles(Collection<Path> files) {
     return forEach(b -> b.addLibraryFiles(files));
   }
 
   @Override
-  public TestBuilderCollection addLibraryClasses(Collection<Class<?>> classes) {
+  public T addLibraryClasses(Collection<Class<?>> classes) {
     return forEach(b -> b.addLibraryClasses(classes));
   }
 
   @Override
-  public TestBuilderCollection addClasspathClasses(Collection<Class<?>> classes) {
+  public T addClasspathClasses(Collection<Class<?>> classes) {
     return forEach(b -> b.addClasspathClasses(classes));
   }
 
   @Override
-  public TestBuilderCollection addClasspathFiles(Collection<Path> files) {
+  public T addClasspathFiles(Collection<Path> files) {
     return forEach(b -> b.addClasspathFiles(files));
   }
 
   @Override
-  public TestBuilderCollection addRunClasspathFiles(Collection<Path> files) {
+  public T addRunClasspathFiles(Collection<Path> files) {
     return forEach(b -> b.addRunClasspathFiles(files));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestRunResultCollection.java b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
index af4256a..4912ef8 100644
--- a/src/test/java/com/android/tools/r8/TestRunResultCollection.java
+++ b/src/test/java/com/android/tools/r8/TestRunResultCollection.java
@@ -6,70 +6,81 @@
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import org.hamcrest.Matcher;
 
 /** Checking container for checking the same properties of multiple run results. */
-public class TestRunResultCollection extends TestRunResult<TestRunResultCollection> {
+public abstract class TestRunResultCollection<
+        C extends Enum<C>, RR extends TestRunResultCollection<C, RR>>
+    extends TestRunResult<RR> {
 
-  public static TestRunResultCollection create(List<Pair<String, TestRunResult<?>>> runs) {
-    assert !runs.isEmpty();
-    return new TestRunResultCollection(runs);
-  }
+  private final List<Pair<C, TestRunResult<?>>> runs;
 
-  private final List<Pair<String, TestRunResult<?>>> runs;
-
-  public TestRunResultCollection(List<Pair<String, TestRunResult<?>>> runs) {
+  public TestRunResultCollection(List<Pair<C, TestRunResult<?>>> runs) {
     this.runs = runs;
   }
 
-  @Override
-  TestRunResultCollection self() {
-    return this;
-  }
-
-  private TestRunResultCollection forEach(Consumer<TestRunResult<?>> fn) {
+  private RR forEach(Consumer<TestRunResult<?>> fn) {
     runs.forEach(r -> fn.accept(r.getSecond()));
     return self();
   }
 
   @Override
-  public TestRunResultCollection assertSuccess() {
+  public RR assertSuccess() {
     return forEach(TestRunResult::assertSuccess);
   }
 
   @Override
-  public TestRunResultCollection assertFailure() {
+  public RR assertFailure() {
     return forEach(TestRunResult::assertFailure);
   }
 
   @Override
-  public TestRunResultCollection assertStdoutMatches(Matcher<String> matcher) {
+  public RR assertStdoutMatches(Matcher<String> matcher) {
     return forEach(r -> r.assertStdoutMatches(matcher));
   }
 
   @Override
-  public TestRunResultCollection assertStderrMatches(Matcher<String> matcher) {
+  public RR assertStderrMatches(Matcher<String> matcher) {
     return forEach(r -> r.assertStderrMatches(matcher));
   }
 
   @Override
-  public <E extends Throwable> TestRunResultCollection inspect(
-      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, ExecutionException, E {
-    for (Pair<String, TestRunResult<?>> run : runs) {
-      run.getSecond().inspect(consumer);
+  public <E extends Throwable> RR inspect(ThrowingConsumer<CodeInspector, E> consumer)
+      throws IOException, ExecutionException, E {
+    return inspectIf(Predicates.alwaysTrue(), consumer);
+  }
+
+  public RR applyIf(Predicate<C> filter, Consumer<TestRunResult<?>> fn) {
+    for (Pair<C, TestRunResult<?>> run : runs) {
+      if (filter.test(run.getFirst())) {
+        fn.accept(run.getSecond());
+      }
+    }
+    return self();
+  }
+
+  public <E extends Throwable> RR inspectIf(
+      Predicate<C> filter, ThrowingConsumer<CodeInspector, E> consumer)
+      throws IOException, ExecutionException, E {
+    for (Pair<C, TestRunResult<?>> run : runs) {
+      if (filter.test(run.getFirst())) {
+        run.getSecond().inspect(consumer);
+      }
     }
     return self();
   }
 
   @Override
-  public TestRunResultCollection disassemble() throws IOException, ExecutionException {
-    for (Pair<String, TestRunResult<?>> run : runs) {
-      String name = run.getFirst();
+  public RR disassemble() throws IOException, ExecutionException {
+    for (Pair<C, TestRunResult<?>> run : runs) {
+      String name = run.getFirst().name();
       System.out.println(name + " " + Strings.repeat("=", 80 - name.length() - 1));
       run.getSecond().disassemble();
     }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 7de9a9a..e6fd9e0 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -121,6 +121,9 @@
   public static final String JAVA_CLASSES_DIR = BUILD_DIR + "classes/java/";
   public static final String JDK_11_TESTS_CLASSES_DIR = JAVA_CLASSES_DIR + "jdk11Tests/";
 
+  public static final String ASM_JAR = BUILD_DIR + "deps/asm-8.0.jar";
+  public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-8.0.jar";
+
   public static final Path API_SAMPLE_JAR = Paths.get("tests", "r8_api_usage_sample.jar");
 
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
index 2cb3e95..7521316 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
@@ -8,8 +8,8 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -31,7 +31,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-@NeverMerge
+@NoVerticalClassMerging
 class MySerializable implements Serializable {
 
   @NeverPropagateValue transient int value;
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/DataAdapter.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/DataAdapter.java
index 5209511..a4d94bb 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/DataAdapter.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/DataAdapter.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.bridgeremoval.bridgestokeep;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
 public interface DataAdapter {
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface Observer extends ObservableList.Observer {}
 
   void registerObserver(Observer observer);
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 3b7481b..0e373e9 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -54,7 +54,7 @@
         .enableNeverClassInliningAnnotations()
         // TODO(b/120764902): MemberSubject.getOriginalName() is not working without the @NeverMerge
         //  annotation on DataAdapter.Observer.
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableProguardTestOptions()
         .minification(minification)
         .setMinApi(parameters.getApiLevel())
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 410d418..c9f7d21 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
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -72,7 +72,7 @@
                 .transform())
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -113,7 +113,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends BridgeHoistingAccessibilityTestClasses.A {
 
     // This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
@@ -136,7 +136,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class BWithRangedInvoke extends AWithRangedInvoke {
 
     // This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/InterfaceBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/InterfaceBridgeHoistingTest.java
index 9fdc87f..0e79433 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/InterfaceBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/InterfaceBridgeHoistingTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +38,7 @@
                 .transform())
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -53,10 +53,10 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/KeptBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/KeptBridgeHoistingTest.java
index b4ce765..fc6ac6b 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/KeptBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/KeptBridgeHoistingTest.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,7 +43,7 @@
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-keep class " + B.class.getTypeName() + " { java.lang.String bridge(...); }")
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -69,7 +69,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
index d93514a..02c948c 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
@@ -43,7 +43,7 @@
             transformer(C.class).setBridge(C.class.getDeclaredMethod("bridge")).transform())
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
index 87310d2..f2a91a8 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,7 +43,7 @@
                 .transform())
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -70,7 +70,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {}
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
index 55daf92..9990f62 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -5,13 +5,13 @@
 package com.android.tools.r8.bridgeremoval.hoisting.testclasses;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest;
 import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest.CWithRangedInvoke;
 
 public class BridgeHoistingAccessibilityTestClasses {
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class A {
 
     @NeverInline
@@ -28,7 +28,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class AWithRangedInvoke {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/NonReboundBridgeHoistingTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/NonReboundBridgeHoistingTestClasses.java
index f66c6a3..c419f7f 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/NonReboundBridgeHoistingTestClasses.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/NonReboundBridgeHoistingTestClasses.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.bridgeremoval.hoisting.testclasses;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
 public class NonReboundBridgeHoistingTestClasses {
 
@@ -13,7 +13,7 @@
     return A.class;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
@@ -22,6 +22,6 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class B extends A {}
 }
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
index c0067d6..690dd6b 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.cf;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
@@ -82,7 +82,7 @@
     // Class that is only mentioned in return value of LDC(MethodType)-instruction.
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     int ii = 42;
 
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 3062ae8..2a6652b 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -177,7 +177,7 @@
       builder.addProguardConfiguration(
           Arrays.asList(
               keepMainProguardConfiguration(MethodHandleTest.class),
-              neverMergeRule(),
+              noVerticalClassMergingRule(),
               // Prevent the second argument of C.svic(), C.sjic(), I.sjic() and I.svic() from
               // being removed although they are never used unused. This is needed since these
               // methods are accessed reflectively.
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
index 3f5b778..e2defd2 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedOverriddenMethodTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -48,7 +48,7 @@
               "-checkdiscard class **.*$" + targetClass.getSimpleName() + " { void gone(); }")
           .enableNeverClassInliningAnnotations()
           .enableInliningAnnotations()
-          .enableMergeAnnotations()
+          .enableNoVerticalClassMergingAnnotations()
           .minification(minification)
           .setMinApi(parameters.getRuntime())
           // Asserting that -checkdiscard is not giving any information out on an un-removed
@@ -70,7 +70,7 @@
     test(TestMain2.class, Itf.class);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Base {
     @NeverInline
     void gone() {
@@ -93,7 +93,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface Itf {
     void gone();
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java
index edd84cb..5eb49b1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 
@@ -27,7 +27,7 @@
         .addKeepMainRule(Main.class)
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -41,7 +41,7 @@
             });
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   public interface I {
     void bar();
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java
index c6f58af..70e7c0d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 
@@ -28,7 +28,7 @@
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -42,7 +42,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void bar();
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
new file mode 100644
index 0000000..075448a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -0,0 +1,76 @@
+// 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 org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class InnerOuterClassesTest extends HorizontalClassMergingTestBase {
+  public InnerOuterClassesTest(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)
+        .enableNeverClassInliningAnnotations()
+        .addKeepAttributes("InnerClasses", "EnclosingMethod")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "b", "c", "d")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(A.B.class), isPresent());
+              assertThat(codeInspector.clazz(C.class), isPresent());
+              assertThat(codeInspector.clazz(A.D.class), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+
+    @NeverClassInline
+    public static class B {
+      public B() {
+        System.out.println("b");
+      }
+    }
+
+    @NeverClassInline
+    public static class D {
+      public D() {
+        System.out.println("d");
+      }
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    public C() {
+      System.out.println("c");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      A.B b = new A.B();
+      C c = new C();
+      A.D d = new A.D();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
index 1f0daeb..fc4f076 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
@@ -10,7 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.naming.retrace.StackTrace;
@@ -47,7 +47,7 @@
         .addKeepAttributeSourceFile()
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -77,7 +77,7 @@
             });
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Parent {
     Parent() {
       if (System.currentTimeMillis() >= 0) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAnnotationsTest.java
new file mode 100644
index 0000000..b7fc7a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAnnotationsTest.java
@@ -0,0 +1,91 @@
+// 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 org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+
+public class NoAnnotationsTest extends HorizontalClassMergingTestBase {
+  public NoAnnotationsTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(TypeAnnotation.class, MethodAnnotation.class)
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "b", "c", "foo")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(codeInspector.clazz(C.class), isPresent());
+            });
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface TypeAnnotation {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD})
+  public @interface MethodAnnotation {}
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+  }
+
+  @TypeAnnotation
+  @NeverClassInline
+  public static class B {
+    public B(String v) {
+      System.out.println(v);
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    public C(String v) {
+      System.out.println(v);
+    }
+
+    @NeverInline
+    @MethodAnnotation
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B("b");
+      C c = new C("c");
+      c.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
new file mode 100644
index 0000000..dc513c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
@@ -0,0 +1,66 @@
+// 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 org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class NoHorizontalClassMergingTest extends HorizontalClassMergingTestBase {
+  public NoHorizontalClassMergingTest(
+      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)
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "b")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class B {
+    public B(String v) {
+      System.out.println(v);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B("b");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
new file mode 100644
index 0000000..a1b0951
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
@@ -0,0 +1,74 @@
+// 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.testclasses.A;
+import com.android.tools.r8.classmerging.horizontal.testclasses.B;
+import org.junit.Test;
+
+public class PackagePrivateMemberAccessTest extends HorizontalClassMergingTestBase {
+  public PackagePrivateMemberAccessTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(A.class)
+        .addProgramClasses(B.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .allowAccessModification(false)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo", "B", "bar", "5", "foobar")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+                assertThat(codeInspector.clazz(C.class), isPresent());
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+                assertThat(codeInspector.clazz(C.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class C {
+    public C(int v) {
+      System.out.println(v);
+    }
+
+    public void foobar() {
+      System.out.println("foobar");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      a.foo();
+      B b = a.get("B");
+      b.bar();
+      C c = new C(5);
+      c.foobar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
new file mode 100644
index 0000000..cdaeee7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
@@ -0,0 +1,66 @@
+// 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 org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.testclasses.C;
+import com.android.tools.r8.classmerging.horizontal.testclasses.D;
+import org.junit.Test;
+
+public class PackagePrivateMembersAccessedTest extends HorizontalClassMergingTestBase {
+  public PackagePrivateMembersAccessedTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(C.class)
+        .addProgramClasses(D.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .allowAccessModification(false)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo", "hello", "5", "foobar")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(C.class), isPresent());
+              assertThat(codeInspector.clazz(D.class), isPresent());
+              assertThat(codeInspector.clazz(E.class), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class E {
+    public E(int v) {
+      System.out.println(v);
+    }
+
+    public void foobar() {
+      System.out.println("foobar");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      D d = new D("foo");
+      C c = d.getC("hello");
+      c.bar();
+      E e = new E(5);
+      e.foobar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
new file mode 100644
index 0000000..444d573
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
@@ -0,0 +1,65 @@
+// 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 org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class PinnedClassMemberTest extends HorizontalClassMergingTestBase {
+  public PinnedClassMemberTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-keepclassmembers class " + B.class.getTypeName() + " { void foo(); }")
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "b", "foo", "true")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(B.class), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B(String s) {
+      System.out.println(s);
+    }
+
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) throws Exception {
+      A a = new A();
+      B b = new B("b");
+      b.foo();
+
+      System.out.println(b.getClass().getMethod("foo").getName().equals("foo"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassTest.java
new file mode 100644
index 0000000..a2b36f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassTest.java
@@ -0,0 +1,61 @@
+// 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 org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class PinnedClassTest extends HorizontalClassMergingTestBase {
+  public PinnedClassTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(B.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "b", "true", "true")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(B.class), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B(String s) {
+      System.out.println(s);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B("b");
+
+      System.out.println(b.getClass().getSimpleName().equals("PinnedClassTest$B"));
+      System.out.println(!a.getClass().getSimpleName().equals("PinnedClassTest$B"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ReferencedInAnnotationTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ReferencedInAnnotationTest.java
new file mode 100644
index 0000000..620f948
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ReferencedInAnnotationTest.java
@@ -0,0 +1,111 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+
+public class ReferencedInAnnotationTest extends HorizontalClassMergingTestBase {
+
+  public ReferencedInAnnotationTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Annotation.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .addKeepRuntimeVisibleAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // TestClass and A should still be present.
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    // B should have been merged into A if horizontal class merging is enabled.
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+
+    // The annotation on TestClass should now refer to A instead of B.
+    AnnotationSubject annotationSubject =
+        testClassSubject.annotation(Annotation.class.getTypeName());
+    assertThat(annotationSubject, isPresent());
+
+    DexEncodedAnnotation encodedAnnotation = annotationSubject.getAnnotation();
+    assertEquals(1, encodedAnnotation.getNumberOfElements());
+
+    DexValue annotationElementValue = encodedAnnotation.getElement(0).getValue();
+    assertTrue(annotationElementValue.isDexValueType());
+
+    DexType annotationElementValueType = annotationElementValue.asDexValueType().getValue();
+    assertEquals(
+        (enableHorizontalClassMerging ? aClassSubject : bClassSubject)
+            .getDexProgramClass()
+            .getType(),
+        annotationElementValueType);
+  }
+
+  @Annotation(B.class)
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      new A();
+      new B("");
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  @interface Annotation {
+    Class<?> value();
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    public A() {
+      System.out.print("Hello");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+
+    public B(String s) {
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java
index f323ec7..19c458f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 
@@ -28,7 +28,7 @@
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("foo", "parent b", "parent b", "x", "parent b")
@@ -57,7 +57,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   public static class ParentB {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/A.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/A.java
new file mode 100644
index 0000000..ea0e1c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/A.java
@@ -0,0 +1,20 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class A {
+  public void foo() {
+    System.out.println("foo");
+  }
+
+  @NeverInline
+  public B get(String s) {
+    return new B(s);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/B.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/B.java
new file mode 100644
index 0000000..6157508
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/B.java
@@ -0,0 +1,20 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class B {
+  protected B(String a) {
+    System.out.println(a);
+  }
+
+  @NeverInline
+  public void bar() {
+    System.out.println("bar");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/C.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/C.java
new file mode 100644
index 0000000..f2220c0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/C.java
@@ -0,0 +1,22 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class C {
+  String field;
+
+  protected C(String field) {
+    this.field = field;
+  }
+
+  @NeverInline
+  public void bar() {
+    System.out.println(field);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/D.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/D.java
new file mode 100644
index 0000000..3348d96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/D.java
@@ -0,0 +1,20 @@
+// 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.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class D {
+  public D(String v) {
+    System.out.println(v);
+  }
+
+  @NeverInline
+  public C getC(String v) {
+    return new C(v);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
index 8ddfef4..5f4945e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,7 +53,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(HorizontalClassMergerSynchronizedMethodTest.class)
         .addKeepMainRule(Main.class)
-        .enableMergeAnnotations()
+        .enableNoStaticClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
@@ -83,7 +83,7 @@
     }
   }
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class LockThree {
 
     static synchronized void acquire(I c) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithMultiPackageAccessesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithMultiPackageAccessesTest.java
index a96f8d3..d830cdb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithMultiPackageAccessesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithMultiPackageAccessesTest.java
@@ -37,7 +37,7 @@
         .addInnerClasses(ForceInlineConstructorWithMultiPackageAccessesTest.class)
         .addInnerClasses(ForceInlineConstructorWithMultiPackageAccessesTestClasses.class)
         .addKeepMainRule(TestClass.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
index 9de435b..90ec4c3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -57,7 +57,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED)
@@ -82,7 +82,7 @@
         .transform();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class Base {
 
     public void collect() {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedMethodTest.java
index deff6a8..ff081a4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSynchronizedMethodTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,7 +53,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(VerticalClassMergerSynchronizedMethodTest.class)
         .addKeepMainRule(Main.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput("Hello World!")
@@ -73,7 +73,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class LockTwo extends LockOne {
 
     static synchronized void acquire(I c) {
@@ -83,7 +83,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class LockThree {
 
     static synchronized void acquire(I c) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
index 2b3967a..077c5e3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/testclasses/ForceInlineConstructorWithMultiPackageAccessesTestClasses.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.classmerging.vertical.testclasses;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
 public class ForceInlineConstructorWithMultiPackageAccessesTestClasses {
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class A {
 
     protected A() {}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
index 9eb054c..9c006cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -70,7 +70,7 @@
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .compile()
         .inspect(this::inspect)
@@ -86,7 +86,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
     @NeverInline
     static I newInstance() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
index 7192449..b2d5b54 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -5,22 +5,22 @@
 package com.android.tools.r8.desugar;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.DesugarTestConfiguration;
 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 com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,6 +29,14 @@
 @RunWith(Parameterized.class)
 public class DesugarLambdaWithAnonymousClass extends TestBase {
 
+  private List<String> EXPECTED_JAVAC_RESULT =
+      ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
+
+  private List<String> EXPECTED_DESUGARED_RESULT =
+      ImmutableList.of(
+          "Hello from inside lambda$test$0$DesugarLambdaWithAnonymousClass$TestClass",
+          "Hello from inside lambda$testStatic$1");
+
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
@@ -73,111 +81,55 @@
     assertEquals(2, counter.getCount());
   }
 
-  // TODO(158752316): There should be no use of this check.
-  private void checkEnclosingMethodWrong(CodeInspector inspector) {
-    Counter counter = new Counter();
-    inspector.forAllClasses(
-        clazz -> {
-          if (clazz.getFinalName().endsWith("$TestClass$1")
-              || clazz.getFinalName().endsWith("$TestClass$2")) {
-            counter.increment();
-            assertTrue(clazz.isAnonymousClass());
-            DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
-            ClassSubject testClassSubject =
-                inspector.clazz(DesugarLambdaWithAnonymousClass.TestClass.class);
-            assertEquals(
-                testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
-            if (enclosingMethod.name.toString().contains("Static")) {
-              assertThat(
-                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
-                  isPresent());
-            } else {
-              assertThat(
-                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
-                  not(isPresent()));
-            }
-          }
-        });
-    assertEquals(2, counter.getCount());
-  }
-
-  private void checkArtResult(D8TestRunResult result) {
-    // TODO(158752316): This should neither return null nor fail.
-    if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)
-        || parameters.getRuntime().asDex().getVm().getVersion().isNewerThan(Version.V6_0_1)) {
-      result.assertSuccessWithOutputLines(
-          "Hello from inside <null>", "Hello from inside lambda$testStatic$1");
-    } else {
-      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
-    }
-  }
-
   @BeforeClass
   public static void checkExpectedJavacNames() throws Exception {
     CodeInspector inspector =
         new CodeInspector(
-            ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithLocalClass.class));
-    String outer = DesugarLambdaWithLocalClass.class.getTypeName();
+            ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithAnonymousClass.class));
+    String outer = DesugarLambdaWithAnonymousClass.class.getTypeName();
     ClassSubject testClass = inspector.clazz(outer + "$TestClass");
     assertThat(testClass, isPresent());
     assertThat(testClass.uniqueMethodWithName("lambda$test$0"), isPresent());
     assertThat(testClass.uniqueMethodWithName("lambda$testStatic$1"), isPresent());
-    assertThat(inspector.clazz(outer + "$TestClass$1MyConsumerImpl"), isPresent());
-    assertThat(inspector.clazz(outer + "$TestClass$2MyConsumerImpl"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$1"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$2"), isPresent());
   }
 
   @Test
-  public void testDefault() throws Exception {
-    if (parameters.getRuntime().isCf()) {
-      // Run on the JVM.
-      testForJvm()
+  public void testDesugar() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::checkEnclosingMethod)
+        .applyIf(
+            DesugarTestConfiguration::isNotDesugared,
+            r -> r.assertSuccessWithOutputLines(EXPECTED_JAVAC_RESULT))
+        .applyIf(
+            DesugarTestConfiguration::isDesugared,
+            r -> r.assertSuccessWithOutputLines(EXPECTED_DESUGARED_RESULT));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          // TODO(b/158752316, b/157700141): Disable inlining to keep the synthetic lambda methods.
+          .addOptionsModification(options -> options.enableInlining = false)
           .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
-          .run(parameters.getRuntime(), TestClass.class)
+          .setMinApi(parameters.getApiLevel())
+          .addKeepRules("-keep class * { *; }")
+          // Keeping the synthetic lambda methods is currently not supported - they are
+          // forcefully unpinned. The following rule has no effect. See b/b/157700141.
+          .addKeepRules("-keep class **.*$TestClass { synthetic *; }")
+          .addKeepAttributes("InnerClasses", "EnclosingMethod")
+          .compile()
           .inspect(this::checkEnclosingMethod)
-          .assertSuccessWithOutputLines(
-              "Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
-    } else {
-      assert parameters.getRuntime().isDex();
-      // Run on Art.
-      checkArtResult(
-          testForD8()
-              .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
-              .setMinApi(parameters.getApiLevel())
-              .compile()
-              .inspect(this::checkEnclosingMethodWrong)
-              .run(parameters.getRuntime(), TestClass.class));
-    }
-  }
-
-  @Test
-  public void testCfToCf() throws Exception {
-    // Use D8 to desugar with Java classfile output.
-    Path jar =
-        testForD8(Backend.CF)
-            .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::checkEnclosingMethodWrong)
-            .writeToZip();
-
-    if (parameters.getRuntime().isCf()) {
-      // Run on the JVM.
-      testForJvm()
-          .addProgramFiles(jar)
           .run(parameters.getRuntime(), TestClass.class)
-          // TODO(158752316): This should not fail.
-          .assertFailureWithErrorThatThrows(InternalError.class);
-    } else {
-      assert parameters.getRuntime().isDex();
-      // Compile to DEX without desugaring and run on Art.
-      checkArtResult(
-          testForD8()
-              .addProgramFiles(jar)
-              .setMinApi(parameters.getApiLevel())
-              .disableDesugaring()
-              .compile()
-              .inspect(this::checkEnclosingMethodWrong)
-              .run(parameters.getRuntime(), TestClass.class));
+          .assertSuccessWithOutputLines(
+              parameters.isCfRuntime() ? EXPECTED_JAVAC_RESULT : EXPECTED_DESUGARED_RESULT);
+      assertFalse(parameters.isDexRuntime());
+    } catch (AssertionError e) {
+      assertTrue(parameters.isDexRuntime());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
index 68fd89e..8c539d2 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
@@ -5,22 +5,21 @@
 package com.android.tools.r8.desugar;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.DesugarTestConfiguration;
 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 com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
+import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,6 +28,17 @@
 @RunWith(Parameterized.class)
 public class DesugarLambdaWithLocalClass extends TestBase {
 
+  private List<String> EXPECTED_JAVAC_RESULT =
+      ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
+
+  private List<String> EXPECTED_DESUGARED_RESULT =
+      ImmutableList.of(
+          "Hello from inside lambda$test$0$DesugarLambdaWithLocalClass$TestClass",
+          "Hello from inside lambda$testStatic$1");
+
+  private List<String> EXPECTED_DESUGARED_RESULT_R8_WITHOUT_INLINING =
+      ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
+
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
@@ -56,7 +66,8 @@
     Counter counter = new Counter();
     inspector.forAllClasses(
         clazz -> {
-          if (clazz.getFinalName().endsWith("MyConsumerImpl")) {
+          if (clazz.getFinalName().endsWith("$TestClass$1MyConsumerImpl")
+              || clazz.getFinalName().endsWith("$TestClass$2MyConsumerImpl")) {
             counter.increment();
             assertTrue(clazz.isLocalClass());
             DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
@@ -71,111 +82,53 @@
     assertEquals(2, counter.getCount());
   }
 
-  // TODO(158752316): There should be no use of this check.
-  private void checkEnclosingMethodWrong(CodeInspector inspector) {
-    Counter counter = new Counter();
-    inspector.forAllClasses(
-        clazz -> {
-          if (clazz.getFinalName().endsWith("$TestClass$1MyConsumerImpl")
-              || clazz.getFinalName().endsWith("$TestClass$2MyConsumerImpl")) {
-            counter.increment();
-            assertTrue(clazz.isLocalClass());
-            DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
-            ClassSubject testClassSubject = inspector.clazz(TestClass.class);
-            assertEquals(
-                testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
-            if (enclosingMethod.name.toString().contains("Static")) {
-              assertThat(
-                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
-                  isPresent());
-            } else {
-              assertThat(
-                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
-                  not(isPresent()));
-            }
-          }
-        });
-    assertEquals(2, counter.getCount());
-  }
-
-  private void checkArtResult(D8TestRunResult result) {
-    // TODO(158752316): This should neither return null nor fail.
-    if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)
-        || parameters.getRuntime().asDex().getVm().getVersion().isNewerThan(Version.V6_0_1)) {
-      result.assertSuccessWithOutputLines(
-          "Hello from inside <null>", "Hello from inside lambda$testStatic$1");
-    } else {
-      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
-    }
-  }
-
   @BeforeClass
   public static void checkExpectedJavacNames() throws Exception {
     CodeInspector inspector =
         new CodeInspector(
-            ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithAnonymousClass.class));
-    String outer = DesugarLambdaWithAnonymousClass.class.getTypeName();
+            ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithLocalClass.class));
+    String outer = DesugarLambdaWithLocalClass.class.getTypeName();
     ClassSubject testClass = inspector.clazz(outer + "$TestClass");
     assertThat(testClass, isPresent());
     assertThat(testClass.uniqueMethodWithName("lambda$test$0"), isPresent());
     assertThat(testClass.uniqueMethodWithName("lambda$testStatic$1"), isPresent());
-    assertThat(inspector.clazz(outer + "$TestClass$1"), isPresent());
-    assertThat(inspector.clazz(outer + "$TestClass$2"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$1MyConsumerImpl"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$2MyConsumerImpl"), isPresent());
   }
 
   @Test
-  public void testDefault() throws Exception {
-    if (parameters.getRuntime().isCf()) {
-      // Run on the JVM.
-      testForJvm()
-          .addInnerClasses(DesugarLambdaWithLocalClass.class)
-          .run(parameters.getRuntime(), TestClass.class)
-          .inspect(this::checkEnclosingMethod)
-          .assertSuccessWithOutputLines(
-              "Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
-    } else {
-      assert parameters.getRuntime().isDex();
-      // Run on Art.
-      checkArtResult(
-          testForD8()
-              .addInnerClasses(DesugarLambdaWithLocalClass.class)
-              .setMinApi(parameters.getApiLevel())
-              .compile()
-              .inspect(this::checkEnclosingMethodWrong)
-              .run(parameters.getRuntime(), TestClass.class));
-    }
+  public void testDesugar() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(DesugarLambdaWithLocalClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::checkEnclosingMethod)
+        .applyIf(
+            DesugarTestConfiguration::isNotDesugared,
+            r -> r.assertSuccessWithOutputLines(EXPECTED_JAVAC_RESULT))
+        .applyIf(
+            DesugarTestConfiguration::isDesugared,
+            r -> r.assertSuccessWithOutputLines(EXPECTED_DESUGARED_RESULT));
   }
 
   @Test
-  public void testCfToCf() throws Exception {
-    // Use D8 to desugar with Java classfile output.
-    Path jar =
-        testForD8(Backend.CF)
-            .addInnerClasses(DesugarLambdaWithLocalClass.class)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspect(this::checkEnclosingMethodWrong)
-            .writeToZip();
-
-    if (parameters.getRuntime().isCf()) {
-      // Run on the JVM.
-      testForJvm()
-          .addProgramFiles(jar)
-          .run(parameters.getRuntime(), TestClass.class)
-          // TODO(158752316): This should not fail.
-          .assertFailureWithErrorThatThrows(InternalError.class);
-    } else {
-      assert parameters.getRuntime().isDex();
-      // Compile to DEX without desugaring and run on Art.
-      checkArtResult(
-          testForD8()
-              .addProgramFiles(jar)
-              .setMinApi(parameters.getApiLevel())
-              .disableDesugaring()
-              .compile()
-              .inspect(this::checkEnclosingMethodWrong)
-              .run(parameters.getRuntime(), TestClass.class));
-    }
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        // TODO(b/158752316, b/157700141): Disable inlining to keep the synthetic lambda methods.
+        .addOptionsModification(options -> options.enableInlining = false)
+        .addInnerClasses(DesugarLambdaWithLocalClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRules("-keep class * { *; }")
+        // Keeping the synthetic lambda methods is currently not supported - they are
+        // forcefully unpinned. The following rule has no effect. See b/b/157700141.
+        .addKeepRules("-keep class **.*$TestClass { synthetic *; }")
+        .addKeepAttributes("InnerClasses", "EnclosingMethod")
+        .compile()
+        .inspect(this::checkEnclosingMethod)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            parameters.isCfRuntime()
+                ? EXPECTED_JAVAC_RESULT
+                : EXPECTED_DESUGARED_RESULT_R8_WITHOUT_INLINING);
   }
 
   public interface MyConsumer<T> {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileBackport.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileBackport.java
new file mode 100644
index 0000000..e07089f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileBackport.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.desugar;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.code.AddLong2Addr;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarToClassFileBackport extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DesugarToClassFileBackport(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean isCfLAdd(CfInstruction instruction) {
+    return (instruction instanceof CfArithmeticBinop)
+        && (((CfArithmeticBinop) instruction).getOpcode() == Opcode.Add)
+        && (((CfArithmeticBinop) instruction).getType() == NumericType.LONG);
+  }
+
+  private boolean isDexAddLong(Instruction instruction) {
+    return instruction instanceof AddLong2Addr;
+  }
+
+  private boolean boxedDoubleIsFiniteInvoke(InstructionSubject instruction) {
+    System.out.println(instruction);
+    if (instruction.isInvokeStatic()) {
+      System.out.println(instruction.getMethod().qualifiedName());
+    }
+    return instruction.isInvokeStatic()
+        && instruction.getMethod().qualifiedName().equals("java.lang.Double.isFinite");
+  }
+
+  private void checkBackportingNotRequired(CodeInspector inspector) {
+    MethodSubject methodSubject = inspector.clazz(TestClass.class).uniqueMethodWithName("main");
+    if (methodSubject.getProgramMethod().getDefinition().getCode().isCfCode()) {
+      CfCode code = methodSubject.getProgramMethod().getDefinition().getCode().asCfCode();
+      assertTrue(code.instructions.stream().noneMatch(this::isCfLAdd));
+    } else {
+      DexCode code = methodSubject.getProgramMethod().getDefinition().getCode().asDexCode();
+      assertTrue(Arrays.stream(code.instructions).noneMatch(this::isDexAddLong));
+    }
+    assertTrue(methodSubject.streamInstructions().anyMatch(this::boxedDoubleIsFiniteInvoke));
+  }
+
+  private void checkBackportingRequired(CodeInspector inspector) {
+    MethodSubject methodSubject = inspector.clazz(TestClass.class).uniqueMethodWithName("main");
+    if (methodSubject.getProgramMethod().getDefinition().getCode().isCfCode()) {
+      CfCode code = methodSubject.getProgramMethod().getDefinition().getCode().asCfCode();
+      assertTrue(code.instructions.stream().anyMatch(this::isCfLAdd));
+    } else {
+      DexCode code = methodSubject.getProgramMethod().getDefinition().getCode().asDexCode();
+      assertTrue(Arrays.stream(code.instructions).anyMatch(this::isDexAddLong));
+    }
+    assertTrue(methodSubject.streamInstructions().noneMatch(this::boxedDoubleIsFiniteInvoke));
+  }
+
+  private void checkBackportedIfRequired(CodeInspector inspector) {
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
+      checkBackportingNotRequired(inspector);
+    } else {
+      checkBackportingRequired(inspector);
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForDesugaring(parameters)
+        .addInnerClasses(DesugarToClassFileBackport.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspectIf(DesugarTestConfiguration::isNotDesugared, this::checkBackportingNotRequired)
+        .inspectIf(DesugarTestConfiguration::isDesugared, this::checkBackportedIfRequired)
+        .assertSuccessWithOutputLines("3", "true");
+  }
+
+  static class TestClass {
+
+    private static long getOne() {
+      return 1L;
+    }
+
+    private static long getTwo() {
+      return 2L;
+    }
+
+    public static void main(String[] args) {
+      System.out.println(Long.sum(getOne(), getTwo()));
+      System.out.println(Double.isFinite(1.0));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
new file mode 100644
index 0000000..2913fe5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -0,0 +1,167 @@
+// 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.desugar.backports;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+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.desugar.backports.AbstractBackportTest.MiniAssert;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BackportDuplicationTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  static final List<Class<?>> CLASSES =
+      ImmutableList.of(MiniAssert.class, TestClass.class, User1.class, User2.class);
+
+  static final List<String> CLASS_TYPE_NAMES =
+      CLASSES.stream().map(Class::getTypeName).collect(Collectors.toList());
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.J).build();
+  }
+
+  public BackportDuplicationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runR8(false);
+    runR8(true);
+  }
+
+  private void runR8(boolean minify) throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(MiniAssert.class)
+        .setMinApi(parameters.getApiLevel())
+        .minification(minify)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoInternalSyntheticNames);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    List<String> run1 = getClassesAfterD8CompileAndRun();
+    List<String> run2 = getClassesAfterD8CompileAndRun();
+    assertEquals("Non deterministic synthesis", run1, run2);
+  }
+
+  private List<String> getClassesAfterD8CompileAndRun() throws Exception {
+    return testForD8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoInternalSyntheticNames)
+        .inspect(this::checkExpectedOutput)
+        .inspector()
+        .allClasses()
+        .stream()
+        .filter(c -> !CLASS_TYPE_NAMES.contains(c.getFinalName()))
+        .flatMap(c -> c.allMethods().stream().map(m -> m.asMethodReference().toString()))
+        .sorted()
+        .collect(Collectors.toList());
+  }
+
+  private void checkNoInternalSyntheticNames(CodeInspector inspector) {
+    inspector.forAllClasses(
+        clazz -> {
+          assertThat(
+              clazz.getFinalName(),
+              not(containsString(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
+        });
+  }
+
+  private void checkExpectedOutput(CodeInspector inspector) {
+    // TODO(b/158159959): Once synthetic methods can be grouped in classes this should become 1.
+    int expectedSynthesizedClasses = 3;
+    // Total number of synthetic methods should be 3 ({Boolean,Character,Long}.compare).
+    int expectedSynthesizedMethods = 3;
+    // Desugaring should add exactly one class with one desugared method.
+    assertEquals(expectedSynthesizedClasses, inspector.allClasses().size() - CLASSES.size());
+    assertThat(
+        inspector.allClasses().stream()
+            .map(ClassSubject::getOriginalName)
+            .collect(Collectors.toList()),
+        hasItems(CLASS_TYPE_NAMES.toArray()));
+    List<FoundMethodSubject> methods =
+        inspector.allClasses().stream()
+            .filter(clazz -> !CLASS_TYPE_NAMES.contains(clazz.getOriginalName()))
+            .flatMap(clazz -> clazz.allMethods().stream())
+            .collect(Collectors.toList());
+    assertEquals(expectedSynthesizedMethods, methods.size());
+  }
+
+  static class User1 {
+
+    private static void testBooleanCompare() {
+      // These 4 calls should share the same synthetic method.
+      MiniAssert.assertTrue(Boolean.compare(true, false) > 0);
+      MiniAssert.assertTrue(Boolean.compare(true, true) == 0);
+      MiniAssert.assertTrue(Boolean.compare(false, false) == 0);
+      MiniAssert.assertTrue(Boolean.compare(false, true) < 0);
+    }
+
+    private static void testCharacterCompare() {
+      // All 6 (User1 and User2) calls should share the same synthetic method.
+      MiniAssert.assertTrue(Character.compare('b', 'a') > 0);
+      MiniAssert.assertTrue(Character.compare('a', 'a') == 0);
+      MiniAssert.assertTrue(Character.compare('a', 'b') < 0);
+    }
+  }
+
+  static class User2 {
+
+    private static void testCharacterCompare() {
+      // All 6 (User1 and User2) calls should share the same synthetic method.
+      MiniAssert.assertTrue(Character.compare('y', 'x') > 0);
+      MiniAssert.assertTrue(Character.compare('x', 'x') == 0);
+      MiniAssert.assertTrue(Character.compare('x', 'y') < 0);
+    }
+
+    private static void testIntegerCompare() {
+      // These 3 calls should share the same synthetic method.
+      MiniAssert.assertTrue(Integer.compare(2, 0) > 0);
+      MiniAssert.assertTrue(Integer.compare(0, 0) == 0);
+      MiniAssert.assertTrue(Integer.compare(0, 2) < 0);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      User1.testBooleanCompare();
+      User1.testCharacterCompare();
+      User2.testCharacterCompare();
+      User2.testIntegerCompare();
+      System.out.println("Hello, world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
new file mode 100644
index 0000000..3be0a2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -0,0 +1,253 @@
+// 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.desugar.backports;
+
+import static com.android.tools.r8.synthesis.SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest.MiniAssert;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BackportMainDexTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  static final List<Class<?>> CLASSES =
+      ImmutableList.of(MiniAssert.class, TestClass.class, User1.class, User2.class);
+
+  static final String SyntheticUnderUser1 =
+      User1.class.getTypeName() + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
+  static final String SyntheticUnderUser2 =
+      User2.class.getTypeName() + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withApiLevel(AndroidApiLevel.J).build();
+  }
+
+  public BackportMainDexTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private String[] getRunArgs() {
+    // Only call User1 methods on runtimes with native multidex.
+    if (parameters.isCfRuntime()
+        || parameters
+            .getRuntime()
+            .asDex()
+            .getMinApiLevel()
+            .isGreaterThanOrEqualTo(apiLevelWithNativeMultiDexSupport())) {
+      return new String[] {User1.class.getTypeName()};
+    }
+    return new String[0];
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(CLASSES)
+        .run(parameters.getRuntime(), TestClass.class, getRunArgs())
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    Set<String> mainDex1 = runD8();
+    Set<String> mainDex2 = runD8();
+    assertEquals("Expected deterministic main-dex lists", mainDex1, mainDex2);
+  }
+
+  private Set<String> runD8() throws Exception {
+    MainDexConsumer mainDexConsumer = new MainDexConsumer();
+    testForD8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .addMainDexListClasses(MiniAssert.class, TestClass.class, User2.class)
+        .setProgramConsumer(mainDexConsumer)
+        .compile()
+        .inspect(
+            inspector -> {
+              // Note: This will change if we group methods in classes, in which case we should
+              // preferable not put non-main-dex referenced methods in the main-dex group.
+              // User1 has two synthetics, one shared with User2 and one for self.
+              assertEquals(
+                  2,
+                  inspector.allClasses().stream()
+                      .filter(c -> c.getFinalName().startsWith(SyntheticUnderUser1))
+                      .count());
+              // User2 has one synthetic as the shared call is placed in User1.
+              assertEquals(
+                  1,
+                  inspector.allClasses().stream()
+                      .filter(c -> c.getFinalName().startsWith(SyntheticUnderUser2))
+                      .count());
+            })
+        .run(parameters.getRuntime(), TestClass.class, getRunArgs())
+        .assertSuccessWithOutput(EXPECTED);
+    checkMainDex(mainDexConsumer);
+    return mainDexConsumer.mainDexDescriptors;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Set<String> mainDex1 = runR8();
+    if (parameters.isDexRuntime()) {
+      Set<String> mainDex2 = runR8();
+      assertEquals(mainDex1, mainDex2);
+    }
+  }
+
+  private Set<String> runR8() throws Exception {
+    MainDexConsumer mainDexConsumer = parameters.isDexRuntime() ? new MainDexConsumer() : null;
+    testForR8(parameters.getBackend())
+        .debug() // Use debug mode to force a minimal main dex.
+        .noMinification() // Disable minification so we can inspect the synthetic names.
+        .applyIf(mainDexConsumer != null, b -> b.setProgramConsumer(mainDexConsumer))
+        .addProgramClasses(CLASSES)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(MiniAssert.class)
+        .addMainDexClassRules(MiniAssert.class, TestClass.class)
+        .addKeepMethodRules(
+            Reference.methodFromMethod(User1.class.getMethod("testBooleanCompare")),
+            Reference.methodFromMethod(User1.class.getMethod("testCharacterCompare")))
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class, getRunArgs())
+        .assertSuccessWithOutput(EXPECTED);
+    if (mainDexConsumer != null) {
+      checkMainDex(mainDexConsumer);
+      return mainDexConsumer.mainDexDescriptors;
+    }
+    return null;
+  }
+
+  private void checkMainDex(MainDexConsumer mainDexConsumer) throws Exception {
+    AndroidApp mainDexApp =
+        AndroidApp.builder()
+            .addDexProgramData(mainDexConsumer.mainDexBytes, Origin.unknown())
+            .build();
+    CodeInspector mainDexInspector = new CodeInspector(mainDexApp);
+
+    // The program classes in the main-dex list must be in main-dex.
+    assertThat(mainDexInspector.clazz(MiniAssert.class), isPresent());
+    assertThat(mainDexInspector.clazz(TestClass.class), isPresent());
+    assertThat(mainDexInspector.clazz(User2.class), isPresent());
+
+    // At least one synthetic class placed under User2 must be included in the main-dex file.
+    assertEquals(
+        1,
+        mainDexInspector.allClasses().stream()
+            .filter(c -> c.getFinalName().startsWith(SyntheticUnderUser2))
+            .count());
+
+    // Minimal main dex should only include one of the User1 synthetics.
+    assertThat(mainDexInspector.clazz(User1.class), not(isPresent()));
+    assertEquals(
+        1,
+        mainDexInspector.allClasses().stream()
+            .filter(c -> c.getFinalName().startsWith(SyntheticUnderUser1))
+            .count());
+    assertThat(mainDexInspector.clazz(SyntheticUnderUser1), not(isPresent()));
+  }
+
+  static class User1 {
+
+    public static void testBooleanCompare() {
+      // These 4 calls should share the same synthetic method.
+      MiniAssert.assertTrue(Boolean.compare(true, false) > 0);
+      MiniAssert.assertTrue(Boolean.compare(true, true) == 0);
+      MiniAssert.assertTrue(Boolean.compare(false, false) == 0);
+      MiniAssert.assertTrue(Boolean.compare(false, true) < 0);
+    }
+
+    public static void testCharacterCompare() {
+      // All 6 (User1 and User2) calls should share the same synthetic method.
+      MiniAssert.assertTrue(Character.compare('b', 'a') > 0);
+      MiniAssert.assertTrue(Character.compare('a', 'a') == 0);
+      MiniAssert.assertTrue(Character.compare('a', 'b') < 0);
+    }
+  }
+
+  static class User2 {
+
+    public static void testCharacterCompare() {
+      // All 6 (User1 and User2) calls should share the same synthetic method.
+      MiniAssert.assertTrue(Character.compare('y', 'x') > 0);
+      MiniAssert.assertTrue(Character.compare('x', 'x') == 0);
+      MiniAssert.assertTrue(Character.compare('x', 'y') < 0);
+    }
+
+    public static void testIntegerCompare() {
+      // These 3 calls should share the same synthetic method.
+      MiniAssert.assertTrue(Integer.compare(2, 0) > 0);
+      MiniAssert.assertTrue(Integer.compare(0, 0) == 0);
+      MiniAssert.assertTrue(Integer.compare(0, 2) < 0);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      if (args.length == 1) {
+        // Reflectively call the backports on User1 which is not in the main-dex list.
+        Class<?> user1 = Class.forName(args[0]);
+        user1.getMethod("testBooleanCompare").invoke(user1);
+        user1.getMethod("testCharacterCompare").invoke(user1);
+      }
+      User2.testCharacterCompare();
+      User2.testIntegerCompare();
+      System.out.println("Hello, world");
+    }
+  }
+
+  private static class MainDexConsumer implements DexIndexedConsumer {
+
+    byte[] mainDexBytes;
+    Set<String> mainDexDescriptors;
+
+    @Override
+    public void accept(
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+      if (fileIndex == 0) {
+        assertNull(mainDexBytes);
+        assertNull(mainDexDescriptors);
+        mainDexBytes = data.copyByteData();
+        mainDexDescriptors = descriptors;
+      }
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      assertNotNull(mainDexBytes);
+      assertNotNull(mainDexDescriptors);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java b/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java
index cd6d160..77e088c 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/DesugarStaticBackportsOnly.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.StringUtils;
@@ -55,7 +56,7 @@
                     instructionSubject.isInvokeStatic()
                         && instructionSubject
                             .toString()
-                            .contains("$r8$backportedMethods$utility$Long$1$hashCode")));
+                            .contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
     assertEquals(
         parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
         classSubject
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 1b46018..d99b31f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
 
@@ -84,7 +85,7 @@
   }
 
   private void assertCorrect(CodeInspector inspector) {
-    inspector.allClasses().forEach(clazz -> assertTrue(clazz.getOriginalName().startsWith("j$.")));
+    inspector.allClasses().forEach(clazz -> assertThat(clazz.getOriginalName(), startsWith("j$.")));
     assertThat(inspector.clazz("j$.time.Clock"), isPresent());
     // Above N the following classes are removed instead of being desugared.
     if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodTest.java
index adaa55f..b15c928 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.desugaring.interfacemethods;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
@@ -23,7 +23,7 @@
         .addInnerClasses(InvokeSuperInDefaultInterfaceMethodTest.class)
         .addKeepMainRule(TestClass.class)
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(AndroidApiLevel.M)
         .run(TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
@@ -37,7 +37,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     default void m() {
@@ -45,7 +45,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J extends I {
 
     @Override
@@ -55,7 +55,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface K extends I {
 
     // Intentionally does not override I.m().
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 3e3a02c..278d99c 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -13,7 +13,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -56,7 +56,8 @@
           assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
         };
     Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
+        r8FullTestBuilder ->
+            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
     ProcessResult processResult =
         testDexSplitter(
             parameters,
@@ -75,7 +76,8 @@
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
+        r8FullTestBuilder ->
+            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
     ProcessResult processResult =
         testR8Splitter(
             parameters,
@@ -89,7 +91,7 @@
     assertEquals(processResult.stdout, StringUtils.lines("42"));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public abstract static class BaseSuperClass implements RunInterface {
     @Override
     public void run() {
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 5a0fd21..c9dd274 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -13,7 +13,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -60,7 +60,7 @@
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
             r8FullTestBuilder
-                .enableMergeAnnotations()
+                .enableNoVerticalClassMergingAnnotations()
                 .enableInliningAnnotations()
                 .noMinification()
                 .addOptionsModification(o -> o.testing.deterministicSortingBasedOnDexType = true);
@@ -82,7 +82,8 @@
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
+        r8FullTestBuilder ->
+            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
     ProcessResult processResult =
         testR8Splitter(
             parameters,
@@ -96,7 +97,7 @@
     assertTrue(processResult.stdout.equals(StringUtils.lines("42", "foobar")));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class BaseClass implements RunInterface {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
index c290f26..9e116da 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitServiceLoaderTest.java
@@ -5,8 +5,11 @@
 package com.android.tools.r8.dexsplitter;
 
 import static com.android.tools.r8.rewrite.ServiceLoaderRewritingTest.getServiceLoaderLoads;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.R8TestRunResult;
@@ -103,45 +106,31 @@
 
   @Test
   public void testR8AllLoaded() throws Exception {
-    Path base = temp.newFile("base.zip").toPath();
-    Path feature1Path = temp.newFile("feature1.zip").toPath();
-    Path feature2Path = temp.newFile("feature2.zip").toPath();
     R8TestRunResult runResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(Base.class, I.class)
             .setMinApi(parameters.getApiLevel())
             .addKeepMainRule(Base.class)
-            .addFeatureSplit(
-                builder ->
-                    splitWithNonJavaFile(
-                        builder,
-                        feature1Path,
-                        temp,
-                        ImmutableList.of(
-                            new Pair<>(
-                                "META-INF/services/" + I.class.getTypeName(),
-                                StringUtils.lines(Feature1I.class.getTypeName()))),
-                        true,
-                        Feature1I.class))
-            .addFeatureSplit(
-                builder ->
-                    splitWithNonJavaFile(
-                        builder,
-                        feature2Path,
-                        temp,
-                        ImmutableList.of(
-                            new Pair<>(
-                                "META-INF/services/" + I.class.getTypeName(),
-                                StringUtils.lines(Feature2I.class.getTypeName()))),
-                        true,
-                        Feature2I.class))
+            .addFeatureSplitWithResources(
+                ImmutableList.of(
+                    new Pair<>(
+                        "META-INF/services/" + I.class.getTypeName(),
+                        StringUtils.lines(Feature1I.class.getTypeName()))),
+                Feature1I.class)
+            .addFeatureSplitWithResources(
+                ImmutableList.of(
+                    new Pair<>(
+                        "META-INF/services/" + I.class.getTypeName(),
+                        StringUtils.lines(Feature2I.class.getTypeName()))),
+                Feature2I.class)
             .compile()
             .inspect(
-                inspector -> {
-                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
-                })
-            .writeToZip(base)
-            .addRunClasspathFiles(feature1Path, feature2Path)
+                baseInspector -> assertEquals(1, getServiceLoaderLoads(baseInspector, Base.class)),
+                feature1Inspector ->
+                    assertThat(feature1Inspector.clazz(Feature1I.class), isPresent()),
+                feature2Inspector ->
+                    assertThat(feature2Inspector.clazz(Feature2I.class), isPresent()))
+            .addFeatureSplitsToRunClasspathFiles()
             .run(parameters.getRuntime(), Base.class);
     // TODO(b/160888348): This is failing on 7.0
     if (parameters.getRuntime().isDex()
@@ -154,39 +143,29 @@
 
   @Test
   public void testR8WithServiceFileInSeparateFeature() throws Exception {
-    Path base = temp.newFile("base.zip").toPath();
-    Path feature1Path = temp.newFile("feature1.zip").toPath();
-    Path feature2Path = temp.newFile("feature2.zip").toPath();
-    Path feature3Path = temp.newFile("feature3.zip").toPath();
     R8TestRunResult runResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(Base.class, I.class)
             .setMinApi(parameters.getApiLevel())
             .addKeepMainRule(Base.class)
-            .addFeatureSplit(
-                builder -> simpleSplitProvider(builder, feature1Path, temp, Feature1I.class))
-            .addFeatureSplit(
-                builder -> simpleSplitProvider(builder, feature2Path, temp, Feature2I.class))
-            .addFeatureSplit(
-                builder ->
-                    splitWithNonJavaFile(
-                        builder,
-                        feature3Path,
-                        temp,
-                        ImmutableList.of(
-                            new Pair<>(
-                                "META-INF/services/" + I.class.getTypeName(),
-                                StringUtils.lines(
-                                    Feature1I.class.getTypeName(), Feature2I.class.getTypeName()))),
-                        true,
-                        Feature3Dummy.class))
+            .addFeatureSplit(Feature1I.class)
+            .addFeatureSplit(Feature2I.class)
+            .addFeatureSplitWithResources(
+                ImmutableList.of(
+                    new Pair<>(
+                        "META-INF/services/" + I.class.getTypeName(),
+                        StringUtils.lines(
+                            Feature1I.class.getTypeName(), Feature2I.class.getTypeName()))),
+                Feature3Dummy.class)
             .compile()
             .inspect(
-                inspector -> {
-                  assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
-                })
-            .writeToZip(base)
-            .addRunClasspathFiles(feature1Path, feature2Path, feature3Path)
+                baseInspector -> assertEquals(1, getServiceLoaderLoads(baseInspector, Base.class)),
+                feature1Inspector ->
+                    assertThat(feature1Inspector.clazz(Feature1I.class), isPresent()),
+                feature2Inspector ->
+                    assertThat(feature2Inspector.clazz(Feature2I.class), isPresent()),
+                feature3Inspector -> assertTrue(feature3Inspector.allClasses().isEmpty()))
+            .addFeatureSplitsToRunClasspathFiles()
             .run(parameters.getRuntime(), Base.class);
     // TODO(b/160888348): This is failing on 7.0
     if (parameters.getRuntime().isDex()
@@ -199,44 +178,28 @@
 
   @Test
   public void testR8OnlyFeature2() throws Exception {
-    Path base = temp.newFile("base.zip").toPath();
-    Path feature1Path = temp.newFile("feature1.zip").toPath();
-    Path feature2Path = temp.newFile("feature2.zip").toPath();
     testForR8(parameters.getBackend())
         .addProgramClasses(Base.class, I.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Base.class)
-        .addFeatureSplit(
-            builder ->
-                splitWithNonJavaFile(
-                    builder,
-                    feature1Path,
-                    temp,
-                    ImmutableList.of(
-                        new Pair<>(
-                            "META-INF/services/" + I.class.getTypeName(),
-                            StringUtils.lines(Feature1I.class.getTypeName()))),
-                    true,
-                    Feature1I.class))
-        .addFeatureSplit(
-            builder ->
-                splitWithNonJavaFile(
-                    builder,
-                    feature2Path,
-                    temp,
-                    ImmutableList.of(
-                        new Pair<>(
-                            "META-INF/services/" + I.class.getTypeName(),
-                            StringUtils.lines(Feature2I.class.getTypeName()))),
-                    true,
-                    Feature2I.class))
+        .addFeatureSplitWithResources(
+            ImmutableList.of(
+                new Pair<>(
+                    "META-INF/services/" + I.class.getTypeName(),
+                    StringUtils.lines(Feature1I.class.getTypeName()))),
+            Feature1I.class)
+        .addFeatureSplitWithResources(
+            ImmutableList.of(
+                new Pair<>(
+                    "META-INF/services/" + I.class.getTypeName(),
+                    StringUtils.lines(Feature2I.class.getTypeName()))),
+            Feature2I.class)
         .compile()
         .inspect(
-            inspector -> {
-              assertEquals(1, getServiceLoaderLoads(inspector, Base.class));
-            })
-        .writeToZip(base)
-        .addRunClasspathFiles(feature2Path)
+            baseInspector -> assertEquals(1, getServiceLoaderLoads(baseInspector, Base.class)),
+            feature1Inspector -> assertThat(feature1Inspector.clazz(Feature1I.class), isPresent()),
+            feature2Inspector -> assertThat(feature2Inspector.clazz(Feature2I.class), isPresent()))
+        .apply(compileResult -> compileResult.addRunClasspathFiles(compileResult.getFeature(1)))
         .run(parameters.getRuntime(), Base.class)
         // TODO(b/160889305): This should work.
         .assertFailureWithErrorThatMatches(containsString("java.lang.ClassNotFoundException"));
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
index 60477b6..b5940d4 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.ExtractMarker;
 import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -131,12 +132,11 @@
         .setMinApi(parameters.getApiLevel())
         .addFeatureSplit(
             builder ->
-                splitWithNonJavaFile(
-                    builder, feature1Path, temp, nonJavaFiles, true, FeatureClass.class))
+                splitWithNonJavaFile(builder, feature1Path, temp, nonJavaFiles, FeatureClass.class))
         .addFeatureSplit(
             builder ->
                 splitWithNonJavaFile(
-                    builder, feature2Path, temp, nonJavaFiles, true, FeatureClass2.class))
+                    builder, feature2Path, temp, nonJavaFiles, FeatureClass2.class))
         .addKeepAllClassesRule()
         .compile()
         .writeToZip(basePath);
@@ -158,32 +158,23 @@
 
   @Test
   public void testAdaptResourceContentInSplits() throws IOException, CompilationFailedException {
-    Path basePath = temp.newFile("base.zip").toPath();
-    Path feature1Path = temp.newFile("feature1.zip").toPath();
-    Path feature2Path = temp.newFile("feature2.zip").toPath();
     // Make the content of the data resource be class names
     Collection<Pair<String, String>> nonJavaFiles =
         ImmutableList.of(
             new Pair<>(FeatureClass.class.getName(), FeatureClass.class.getName()),
             new Pair<>(FeatureClass2.class.getName(), FeatureClass2.class.getName()));
 
-    testForR8(parameters.getBackend())
-        .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
-        .setMinApi(parameters.getApiLevel())
-        .addFeatureSplit(
-            builder ->
-                splitWithNonJavaFile(
-                    builder, feature1Path, temp, nonJavaFiles, false, FeatureClass.class))
-        .addFeatureSplit(
-            builder ->
-                splitWithNonJavaFile(
-                    builder, feature2Path, temp, nonJavaFiles, false, FeatureClass2.class))
-        .addKeepClassRulesWithAllowObfuscation(
-            BaseClass.class, FeatureClass.class, FeatureClass2.class)
-        .addKeepRules("-adaptresourcefilecontents")
-        .compile()
-        .writeToZip(basePath);
-    for (Path feature : ImmutableList.of(feature1Path, feature2Path)) {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
+            .setMinApi(parameters.getApiLevel())
+            .addFeatureSplitWithResources(nonJavaFiles, FeatureClass.class)
+            .addFeatureSplitWithResources(nonJavaFiles, FeatureClass2.class)
+            .addKeepClassRulesWithAllowObfuscation(
+                BaseClass.class, FeatureClass.class, FeatureClass2.class)
+            .addKeepRules("-adaptresourcefilecontents")
+            .compile();
+    for (Path feature : compileResult.getFeatures()) {
       ZipFile zipFile = new ZipFile(feature.toFile());
       for (Pair<String, String> nonJavaFile : nonJavaFiles) {
         ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
@@ -192,7 +183,7 @@
         assertNotEquals(content, nonJavaFile.getSecond());
       }
     }
-    ZipFile zipFile = new ZipFile(basePath.toFile());
+    ZipFile zipFile = new ZipFile(compileResult.writeToZip().toFile());
     for (Pair<String, String> nonJavaFile : nonJavaFiles) {
       ZipEntry entry = zipFile.getEntry(nonJavaFile.getFirst());
       assertNull(entry);
@@ -259,20 +250,17 @@
 
 
     public CompiledWithFeature invoke() throws IOException, CompilationFailedException {
-      basePath = temp.newFile("base.zip").toPath();
-      feature1Path = temp.newFile("feature1.zip").toPath();
-      feature2Path = temp.newFile("feature2.zip").toPath();
-
-      testForR8(parameters.getBackend())
-          .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
-          .setMinApi(parameters.getApiLevel())
-          .addFeatureSplit(
-              builder -> simpleSplitProvider(builder, feature1Path, temp, FeatureClass.class))
-          .addFeatureSplit(
-              builder -> simpleSplitProvider(builder, feature2Path, temp, FeatureClass2.class))
-          .addKeepAllClassesRule()
-          .compile()
-          .writeToZip(basePath);
+      R8TestCompileResult compileResult =
+          testForR8(parameters.getBackend())
+              .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class)
+              .setMinApi(parameters.getApiLevel())
+              .addFeatureSplit(FeatureClass.class)
+              .addFeatureSplit(FeatureClass2.class)
+              .addKeepAllClassesRule()
+              .compile();
+      basePath = compileResult.writeToZip();
+      feature1Path = compileResult.getFeature(0);
+      feature2Path = compileResult.getFeature(1);
       return this;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
index def7782..46ac75d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -10,7 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -50,7 +50,8 @@
   public void testInlining() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
+        r8FullTestBuilder ->
+            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
     ThrowingConsumer<R8TestCompileResult, Exception> ensureInlined =
         r8TestCompileResult -> {
           // Ensure that isEarly from BaseUtilClass is inlined into the feature
@@ -70,7 +71,7 @@
     assertEquals(processResult.stdout, StringUtils.lines("42"));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public abstract static class BaseSuperClass implements RunInterface {
     @Override
     public void run() {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 816ed12..c41a1d1 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -1,6 +1,5 @@
 package com.android.tools.r8.dexsplitter;
 
-import static junit.framework.TestCase.assertTrue;
 import static junit.framework.TestCase.fail;
 import static org.junit.Assume.assumeTrue;
 
@@ -19,7 +18,6 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.ZipUtils;
@@ -35,7 +33,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
@@ -53,7 +50,7 @@
       Path outputPath,
       TemporaryFolder temp,
       Collection<Class<?>> classes) {
-    addConsumers(builder, outputPath, temp, null, true, classes);
+    addConsumers(builder, outputPath, temp, null, classes);
     return builder.build();
   }
 
@@ -62,9 +59,7 @@
       Path outputPath,
       TemporaryFolder temp,
       Collection<Pair<String, String>> nonJavaResources,
-      boolean ensureClassesInOutput,
       Collection<Class<?>> classes) {
-    List<String> classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
     Path featureJar;
     try {
       featureJar = temp.newFolder().toPath().resolve("feature.jar");
@@ -109,26 +104,18 @@
                   ByteDataView data,
                   Set<String> descriptors,
                   DiagnosticsHandler handler) {
-                if (ensureClassesInOutput) {
-                  for (String descriptor : descriptors) {
-                    assertTrue(
-                        classNames.contains(DescriptorUtils.descriptorToJavaType(descriptor)));
-                  }
-                }
                 super.accept(fileIndex, data, descriptors, handler);
               }
             });
   }
 
-  static FeatureSplit splitWithNonJavaFile(
+  public static FeatureSplit splitWithNonJavaFile(
       FeatureSplit.Builder builder,
       Path outputPath,
       TemporaryFolder temp,
       Collection<Pair<String, String>> nonJavaFiles,
-      boolean ensureClassesInOutput,
       Class<?>... classes) {
-    addConsumers(
-        builder, outputPath, temp, nonJavaFiles, ensureClassesInOutput, Arrays.asList(classes));
+    addConsumers(builder, outputPath, temp, nonJavaFiles, Arrays.asList(classes));
     return builder.build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
index 6483aee..20362c3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
@@ -51,7 +51,7 @@
         .addKeepRuntimeVisibleAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
@@ -83,7 +83,7 @@
     MyEnumArray[] myEnumArray();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @Retention(RetentionPolicy.RUNTIME)
   @interface ClassAnnotation {}
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
index d6dda57..74ee98c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
@@ -43,7 +43,7 @@
             .addKeepMainRule(classToTest)
             .addKeepRules(enumKeepRules.getKeepRules())
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoStaticClassMergingAnnotations()
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
@@ -69,7 +69,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoStaticClassMerging
   static class OtherClass {
     static {
       Switch.otherClassInit = true;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index d1e5b4a..e9a7e5d 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
+import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -93,7 +93,7 @@
             .anyMatch(
                 c ->
                     c.getOriginalName()
-                        .contains(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME)));
+                        .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index 49fc118..227ce07 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
 import com.android.tools.r8.enumunboxing.examplelib2.JavaLibrary2;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
+import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -97,7 +97,7 @@
             .anyMatch(
                 c ->
                     c.getOriginalName()
-                        .contains(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME)));
+                        .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
index fba8006..7311ab2 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
@@ -55,7 +55,7 @@
             .addKeepMainRules(SUCCESSES)
             .addKeepMainRules(FAILURES)
             .noMinification()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRules())
@@ -106,7 +106,7 @@
       C
     }
 
-    @NeverMerge
+    @NoVerticalClassMerging
     interface Itf {}
   }
 
@@ -124,7 +124,7 @@
       C
     }
 
-    @NeverMerge
+    @NoVerticalClassMerging
     interface Itf {
       @NeverInline
       default int ordinal() {
@@ -153,7 +153,7 @@
       }
     }
 
-    @NeverMerge
+    @NoVerticalClassMerging
     interface Itf {
       @NeverInline
       default int method() {
@@ -176,7 +176,7 @@
       C
     }
 
-    @NeverMerge
+    @NoVerticalClassMerging
     interface Itf {
       @NeverInline
       default int method() {
@@ -205,7 +205,7 @@
       }
     }
 
-    @NeverMerge
+    @NoVerticalClassMerging
     interface Itf {
       int method();
     }
@@ -225,7 +225,7 @@
       C
     }
 
-    @NeverMerge
+    @NoVerticalClassMerging
     interface Itf {
       @NeverInline
       default int method() {
@@ -259,7 +259,7 @@
       }
     }
 
-    @NeverMerge
+    @NoVerticalClassMerging
     interface Itf {
       @NeverInline
       default int method() {
diff --git a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
index 9848767..d191976 100644
--- a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -23,10 +23,10 @@
 @RunWith(Parameterized.class)
 public class MissingClassThrowingTest extends TestBase {
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class MissingException extends Exception {}
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class Program {
 
     public static final String EXPECTED_OUTPUT = "Hello world!";
@@ -72,11 +72,9 @@
         .noMinification()
         .noTreeShaking()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoStaticClassMergingAnnotations()
         .debug()
-        .addKeepRules(
-            "-keep class ** { *; }",
-            "-keepattributes *")
+        .addKeepRules("-keep class ** { *; }", "-keepattributes *")
         .compile()
         .addRunClasspathClasses(MissingException.class)
         .run(parameters.getRuntime(), Program.class)
diff --git a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
index 090844e..4be7975 100644
--- a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
@@ -11,7 +11,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -64,7 +64,7 @@
         .addProgramClasses(I.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class);
   }
@@ -77,7 +77,7 @@
             .addProgramClasses(I.class)
             .addProgramClassFileData(getClassWithTransformedInvoked())
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .addOptionsModification(o -> o.testing.allowInvokeErrors = true)
             .addKeepMainRule(Main.class)
             .run(parameters.getRuntime(), Main.class);
@@ -104,7 +104,7 @@
         .transform();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
 
     @NeverInline
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 c4d321a..dbbf99a 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
@@ -10,7 +10,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -47,7 +47,7 @@
         // The class staticizer does not consider the finalize() method.
         .addOptionsModification(options -> options.enableClassStaticizer = false)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
@@ -136,7 +136,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
index f46650f..6d3c6cb 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
@@ -12,7 +12,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -70,7 +70,7 @@
               .addKeepAllClassesRule()
               .addOptionsModification(options -> options.testing.allowTypeErrors = allowTypeErrors)
               .allowDiagnosticWarningMessages()
-              .enableMergeAnnotations()
+              .enableNoVerticalClassMergingAnnotations()
               .setMinApi(parameters.getApiLevel())
               .compileWithExpectedDiagnostics(
                   diagnostics -> {
@@ -133,7 +133,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   abstract static class A {}
 
   static class ASub1 extends A {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
index 0eb5fab..684b669 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
@@ -11,8 +11,8 @@
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -31,7 +31,7 @@
 import org.junit.runners.Parameterized;
 
 class AlwaysThrowNullTestClass {
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Base {
     Object dead;
 
@@ -239,7 +239,7 @@
     R8TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClassesAndInnerClasses(MAIN)
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .enableMemberValuePropagationAnnotations()
             .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java b/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java
index 94de4ba..50fbd54 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,7 +43,7 @@
     testForR8(parameters.getBackend())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addInnerClasses(DefaultInterfaceIssue143628636Test.class)
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(I.class)
@@ -52,7 +52,7 @@
         .assertSuccessWithOutputLines("2", "5");
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   public interface A {
 
@@ -76,7 +76,7 @@
 
   // Make sure this class and the call to h() are never eliminated. It is the *partial* info
   // propagated from h() to f() that results in incorrect call-site optimization info.
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   public static class B implements A {
     public final int x;
@@ -99,7 +99,7 @@
 
   // Make sure this class and the call to h() are never eliminated. It is the *missing* info
   // propagated from h() to f() that results in incorrect call-site optimization info.
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   public static class C implements I {
     public final I i;
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 626f771..964848a 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
@@ -226,7 +226,7 @@
                     .addOptionsModification(this::disableDevirtualization)
                     .enableInliningAnnotations()
                     .enableNeverClassInliningAnnotations()
-                    .enableMergeAnnotations());
+                    .enableNoVerticalClassMergingAnnotations());
 
     ClassSubject mainSubject = inspector.clazz(NonNullParamAfterInvokeInterface.class);
     assertThat(mainSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
index 0c158b9..a36a63c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.callsites;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -32,10 +32,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(HashCodeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
+        .enableNoVerticalClassMergingAnnotations()
+        .addOptionsModification(
+            o -> {
+              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+            })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("10");
@@ -55,7 +56,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
     @Override
     public int hashCode() {
@@ -63,7 +64,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A {
     @Override
     public int hashCode() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
index 2edbb0b..d6bcd2b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeInterfaceWithRefinedReceiverTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -108,7 +108,7 @@
     void m(Object arg);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class B implements I {
     @NeverInline
@@ -149,7 +149,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class C implements I {
     @NeverInline
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 e36941d..43f777d 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
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualWithRefinedReceiverTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -105,7 +105,7 @@
     abstract void m(Object arg);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class B extends A {
     @NeverInline
@@ -146,7 +146,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class C extends A {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
index fb3bc2e..429b5a2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -36,7 +36,7 @@
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(options -> options.enableUnusedInterfaceRemoval = false)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -66,7 +66,7 @@
 
   interface J extends I {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A implements I {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
index 75dd8a8..7c2eeee 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeInterfaceNegativeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -80,7 +80,7 @@
     assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
     void m(String arg);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
index 3ae302a..7be71b2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualNegativeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -91,7 +91,7 @@
     assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class A {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index 91d181e..5b3b309 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -45,7 +45,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualPositiveTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -92,7 +92,7 @@
     assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class A {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index e131be5..51175f8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeDirectPositiveTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -87,8 +87,9 @@
     assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Base {}
+
   static class Sub1 extends Base {}
   static class Sub2 extends Base {}
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
index 4b4c534..31e6779 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeInterfaceNegativeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -96,7 +96,7 @@
   static class Sub1 extends Base {}
   static class Sub2 extends Base {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
     void m(Base arg);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index bda0a3a..4091161 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeInterfacePositiveTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -97,8 +97,9 @@
     assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Base {}
+
   static class Sub1 extends Base {}
   static class Sub2 extends Base {}
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index 6ea0888..ff19f58 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -8,7 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,11 +43,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeStaticPositiveTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
+        .addOptionsModification(
+            o -> {
+              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+            })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1")
@@ -81,8 +82,9 @@
     assertTrue(test.streamInstructions().noneMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Base {}
+
   static class Sub1 extends Base {}
   static class Sub2 extends Base {}
 
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 51145b2..39bfa4c 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
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualNegativeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -104,7 +104,7 @@
   static class Sub1 extends Base {}
   static class Sub2 extends Base {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class A {
     @NeverInline
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 8c17c3a..2595df5 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
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualPositiveTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -95,12 +95,13 @@
     assertTrue(b_m.streamInstructions().noneMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Base {}
+
   static class Sub1 extends Base {}
   static class Sub2 extends Base {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class A {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
index 79a49e0..bc9f10c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeInterfaceNegativeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -84,7 +84,7 @@
     assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
     void m(Object arg);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
index d2181d5..f46869d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,7 +41,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualCascadeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -68,7 +68,7 @@
     assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class A {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index 26bb65e..544cf86 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualNegativeTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -92,7 +92,7 @@
     assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class A {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index 00601a9..2cff135 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualPositiveTest.class)
         .addKeepMainRule(MAIN)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -91,7 +91,7 @@
     assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   static class A {
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/UnresolvableLibraryConstClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/UnresolvableLibraryConstClassTest.java
index 47ae6be..2765bb2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/UnresolvableLibraryConstClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/UnresolvableLibraryConstClassTest.java
@@ -8,7 +8,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -26,9 +26,8 @@
 class LibraryClass {
 }
 
-@NeverMerge
-class ProgramClass1 extends LibraryClass {
-}
+@NoVerticalClassMerging
+class ProgramClass1 extends LibraryClass {}
 
 class ProgramSubClass extends ProgramClass1 {
 }
@@ -114,7 +113,7 @@
         .addProgramClasses(ProgramClass1.class, ProgramClass2.class, ProgramSubClass.class, MAIN)
         .addKeepMainRule(MAIN)
         .noMinification()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
         .setMinApi(parameters.getRuntime())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
index 73d694e..d158c7d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,7 +41,7 @@
             .addInnerClasses(BuilderWithInheritanceTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput("42")
@@ -70,7 +70,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class BuilderBase {
 
     protected int f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithMethodOnParentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithMethodOnParentClassTest.java
index 7076755..a8098cb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithMethodOnParentClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithMethodOnParentClassTest.java
@@ -9,8 +9,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -40,7 +40,7 @@
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -60,7 +60,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class BuilderBase {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java
index 596c9e5..e603587 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithAccessibleStaticGetTest.java
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +38,7 @@
         .addInnerClasses(ClassInlineInstanceInitializerWithAccessibleStaticGetTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -61,7 +61,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class CandidateBase {
 
     final String f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java
index c0f45d4..29a3cb6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithCheckCastTest.java
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +38,7 @@
         .addInnerClasses(ClassInlineInstanceInitializerWithCheckCastTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -59,7 +59,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class CandidateBase {
 
     final String f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java
index 912374d..a39dd98 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIfTest.java
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +38,7 @@
         .addInnerClasses(ClassInlineInstanceInitializerWithIfTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -59,7 +59,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class CandidateBase {
 
     final String f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
index 34e9d5d..26e44d5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
@@ -41,7 +41,7 @@
             ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoStaticClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
index c86e0fc..d1e2e54 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
@@ -8,8 +8,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -39,7 +39,7 @@
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -59,7 +59,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class CandidateBase {
 
     CandidateBase() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java
index 43e2998..a0429d1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInstanceOfTest.java
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +38,7 @@
         .addInnerClasses(ClassInlineInstanceInitializerWithInstanceOfTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -59,7 +59,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class CandidateBase {
 
     final boolean f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java
index 4ccc742..16aac95 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -32,7 +32,7 @@
   private final TestParameters parameters;
   private static final String EXPECTED_OUTPUT = "Hello world";
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class ShouldBeKept {
 
     @NeverInline
@@ -61,15 +61,16 @@
   @Test
   public void testIsKeptWithName()
       throws ExecutionException, CompilationFailedException, IOException {
-    CodeInspector inspector = testForR8(parameters.getBackend())
-        .addInnerClasses(ClassInlineKeepMethodTest.class)
-        .addKeepMainRule(Keeper.class)
-        .addKeepClassAndMembersRules(ShouldBeKept.class)
-        .enableInliningAnnotations()
-        .enableMergeAnnotations()
-        .run(parameters.getRuntime(), Keeper.class)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT)
-        .inspector();
+    CodeInspector inspector =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ClassInlineKeepMethodTest.class)
+            .addKeepMainRule(Keeper.class)
+            .addKeepClassAndMembersRules(ShouldBeKept.class)
+            .enableInliningAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
+            .run(parameters.getRuntime(), Keeper.class)
+            .assertSuccessWithOutput(EXPECTED_OUTPUT)
+            .inspector();
     ClassSubject clazz = inspector.clazz(Keeper.class);
     assertThat(clazz, isPresent());
     MethodSubject main = clazz.uniqueMethodWithName("main");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
index b8b159c..13a5fcc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerWithSimpleSuperTypeTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -48,7 +48,7 @@
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyCandidateIsClassInlined)
@@ -83,10 +83,10 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A {}
 
   static class C extends B {
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 bd87a92..e0bd65e 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
@@ -11,7 +11,7 @@
 import com.android.tools.r8.KeepUnusedArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -50,7 +50,7 @@
                     enableInvokeSuperToInvokeVirtualRewriting)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableUnusedArgumentAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -78,7 +78,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class HelperBase {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningWithImpreciseReceiverTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningWithImpreciseReceiverTypeTest.java
index cfdfcda..973ae87 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningWithImpreciseReceiverTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningWithImpreciseReceiverTypeTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -33,7 +33,7 @@
         .addInnerClasses(ClassInliningWithImpreciseReceiverTypeTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
@@ -51,7 +51,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   abstract static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java
index 2062667..6f5ab71 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapeFromParentConstructorTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -38,7 +38,7 @@
         .addInnerClasses(EscapeFromParentConstructorTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -60,7 +60,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class BuilderBase {
 
     String greeting;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
index 50eeac4..cc0734b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.testclasses;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
 public class ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses {
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class CandidateBase {
 
     public final String f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
index 61d5cf3..28a973c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.optimize.devirtualize;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -46,7 +46,7 @@
         .addInnerClasses(DevirtualizerNonNullRewritingTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
@@ -78,14 +78,14 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface Interface {
 
     @NeverInline
     void method();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTest.java
index 80a6d8d..d4e3985 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.devirtualize;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -11,7 +11,7 @@
   void foo();
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class OneUniqueImplementer implements TestInterface {
   String boo;
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
index a85906b..98628aa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -42,7 +42,7 @@
         .addInnerClasses(InvokeSuperToInvokeVirtualTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -89,7 +89,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningInvokeSuperTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningInvokeSuperTest.java
index 9bc40d7..47dc21e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningInvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningInvokeSuperTest.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -39,14 +39,14 @@
         .addKeepRules("-keepclassmembers class * { void fooCaller(...); }")
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
     int x;
     @NeverInline
@@ -57,7 +57,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A {
     // B#foo is invoked twice by other wrappers in the same class.
     @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineNonReboundFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineNonReboundFieldTest.java
index d0d52ec..b2fe085 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineNonReboundFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineNonReboundFieldTest.java
@@ -28,7 +28,7 @@
                 TestClass.class, Greeter.class, Greeting.class, Greeting.getGreetingBase())
             .addKeepMainRule(TestClass.class)
             .enableNeverClassInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
index a7aa579..f70aebe 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -10,7 +10,7 @@
 import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -54,7 +54,7 @@
             .addInnerClasses(InliningFromCurrentClassTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
@@ -91,7 +91,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     static {
@@ -103,7 +103,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A {
 
     static {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
index ec3c011..a3b0c4a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
@@ -51,7 +51,7 @@
                 neverInline
                     ? ("-neverinline class " + getClassA().getTypeName() + " { method(); }")
                     : "")
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .enableProguardTestOptions()
             .compile()
             .run(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
index c8bb388..ab412be 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
@@ -10,7 +10,7 @@
 import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -31,7 +31,7 @@
             .addInnerClasses(InliningWithClassInitializerTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
@@ -61,7 +61,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     static {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
index 0e4627b..177353f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
@@ -7,7 +7,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -42,7 +42,7 @@
             .addKeepMainRule(TestClass.class)
             .setMinApi(parameters.getApiLevel())
             .enableNeverClassInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .noMinification()
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
@@ -59,7 +59,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     default void hello() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/Greeting.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/Greeting.java
index 636b0ac..717c09f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/Greeting.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/Greeting.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.optimize.inliner.testclasses;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
-@NeverMerge
+@NoVerticalClassMerging
 public class Greeting extends GreetingBase {
 
   public static Class<?> getGreetingBase() {
@@ -14,7 +14,7 @@
   }
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class GreetingBase {
 
   protected String greeting;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java
index afa71fc..c8c60ae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.optimize.inliner.testclasses;
 
 import com.android.tools.r8.ForceInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
 public class InliningIntoVisibilityBridgeTestClasses {
 
@@ -13,7 +13,7 @@
     return InliningIntoVisibilityBridgeTestClassA.class;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class InliningIntoVisibilityBridgeTestClassA {
 
     @ForceInline
@@ -22,7 +22,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class InliningIntoVisibilityBridgeTestClassB
       extends InliningIntoVisibilityBridgeTestClassA {}
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
index c90173e..8bdea50 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,7 +41,7 @@
         .addInnerClasses(LambdaMethodInliningTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addOptionsModification(options -> options.enableClassInlining = false)
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -108,7 +108,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface ImplementedByClass {
 
     void m();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
index 7cf1f42..8473c67 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -42,7 +42,7 @@
         .addInnerClasses(StaticFieldWithRefinedTypeTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(this::verifyMainIsEmpty)
@@ -75,7 +75,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {}
 
   static class B extends A {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
index a59e9a9..9ffd5ee 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -40,7 +40,7 @@
         .addInnerClasses(FieldInitializedByNonConstantArgumentInSuperConstructorTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -82,7 +82,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     int x;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterface.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterface.java
index e20992e..f4ee88c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterface.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullParamInterface.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.nonnull;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
-@NeverMerge
+@NoVerticalClassMerging
 public interface NonNullParamInterface {
   int sum(NotPinnedClass arg1, NotPinnedClass arg2);
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
index 5691577..66c42e8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -63,7 +63,7 @@
     testForR8(parameters.getBackend())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableSideEffectAnnotations()
         .addInnerClasses(B133215941.class)
         .addKeepMainRule(TestClass.class)
@@ -84,12 +84,12 @@
         .assertSuccessWithOutput(expectedOutput);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface Iface {
     void ifaceMethod();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class TestClassSuper {
     @AssumeMayHaveSideEffects
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/SynchronizedCompanionMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/SynchronizedCompanionMethodTest.java
new file mode 100644
index 0000000..8df91f6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/SynchronizedCompanionMethodTest.java
@@ -0,0 +1,85 @@
+// 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.optimize.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SynchronizedCompanionMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SynchronizedCompanionMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertEquals(
+        1,
+        testClassSubject
+            .allMethods(method -> method.isStatic() && !method.isClassInitializer())
+            .size());
+
+    ClassSubject companionClassSubject = inspector.clazz(Companion.class);
+    assertThat(companionClassSubject, isPresent());
+    assertEquals(
+        1,
+        companionClassSubject
+            .allMethods(method -> !method.isStatic() && method.isSynchronized())
+            .size());
+  }
+
+  static class TestClass {
+
+    static final Companion companion = new Companion();
+
+    public static void main(String[] args) {
+      companion.greet();
+    }
+  }
+
+  @NeverClassInline
+  static class Companion {
+
+    @NeverInline
+    public synchronized void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
index 6ca0f7c..9fd146d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
@@ -47,7 +47,7 @@
             .addInnerClasses(InterfaceMethodTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
@@ -76,7 +76,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     Uninstantiated m();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index b5752cc..7890640 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -49,7 +49,7 @@
             .addInnerClasses(NestedInterfaceMethodTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .addOptionsModification(
                 options -> {
                   options.enableDevirtualization = false;
@@ -82,13 +82,13 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     Uninstantiated m();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J extends I {}
 
   static class A implements J {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
index 7423491..167c17b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexMethod;
@@ -53,7 +53,7 @@
             .addInnerClasses(ParameterRewritingTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoStaticClassMergingAnnotations()
             .addKeepRules("-dontobfuscate")
             .addOptionsModification(options -> options.enableClassInlining = false)
             .run(TestClass.class)
@@ -114,10 +114,10 @@
     }
   }
 
-  @NeverMerge
+  @NoStaticClassMerging
   static class Uninstantiated {}
 
-  @NeverMerge
+  @NoStaticClassMerging
   static class Factory {
 
     @NeverInline
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 a4afbb1..7d3eb83 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
@@ -8,7 +8,8 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
@@ -50,7 +51,8 @@
             .addInnerClasses(VoidReturnTypeRewritingTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
+            .enableNoStaticClassMergingAnnotations()
             .addKeepRules("-dontobfuscate")
             .addOptionsModification(options -> options.enableClassInlining = false)
             .run(TestClass.class)
@@ -102,13 +104,14 @@
     }
   }
 
-  @NeverMerge
+  @NoStaticClassMerging
+  @NoVerticalClassMerging
   static class Uninstantiated {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class SubUninstantiated extends Uninstantiated {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Factory {
 
     @NeverInline
@@ -124,7 +127,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class SubFactory extends Factory {
 
     @Override
@@ -135,7 +138,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class SubSubFactory extends SubFactory {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
index 461ec6b..7456d35 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -48,7 +48,7 @@
         .addKeepMainRule(TestClass.class)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .minification(minification)
         .setMinApi(parameters.getRuntime())
         .compile()
@@ -75,7 +75,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
index 08d8c09..2c7fe55 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
@@ -11,7 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -58,7 +58,7 @@
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .minification(minification)
         .setMinApi(parameters.getRuntime())
         .compile()
@@ -115,7 +115,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.java
index 9001472..9a4d4d0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.unusedarguments;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -32,7 +32,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.class)
         .addKeepMainRule(TestClass.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello from A", "Hello from C");
@@ -52,7 +52,7 @@
     void method(Object o);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     public void method(Object unused) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
index 8b2c2a1..413937e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -39,7 +39,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(UnusedInterfaceRemovalTest.class)
         .addKeepMainRule(TestClass.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -77,13 +77,13 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J {
 
     void bar();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index 9a342e1..52131b3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -40,7 +40,7 @@
         .addInnerClasses(UnusedInterfaceWithDefaultMethodTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -78,13 +78,13 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     void m();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J extends I {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
new file mode 100644
index 0000000..3b35a09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -0,0 +1,84 @@
+// 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.kotlin.lambda.b159688129;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+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.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LambdaGroupGCLimitTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final int LAMBDA_HOLDER_LIMIT = 50;
+  private final int LAMBDAS_PER_CLASS_LIMIT = 100;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public LambdaGroupGCLimitTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    String PKG_NAME = LambdaGroupGCLimitTest.class.getPackage().getName();
+    R8FullTestBuilder testBuilder =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+            .setMinApi(parameters.getApiLevel())
+            .noMinification();
+    Path classFiles = temp.newFile("classes.jar").toPath();
+    List<byte[]> classFileData = new ArrayList<>();
+    for (int mainId = 0; mainId < LAMBDA_HOLDER_LIMIT; mainId++) {
+      classFileData.add(MainKtDump.dump(mainId, LAMBDAS_PER_CLASS_LIMIT));
+      for (int lambdaId = 0; lambdaId < LAMBDAS_PER_CLASS_LIMIT; lambdaId++) {
+        classFileData.add(MainKt$main$1Dump.dump(mainId, lambdaId));
+      }
+      testBuilder.addKeepClassAndMembersRules(PKG_NAME + ".MainKt" + mainId);
+    }
+    writeClassFileDataToJar(classFiles, classFileData);
+    R8TestCompileResult compileResult = testBuilder.addProgramFiles(classFiles).compile();
+    Path path = compileResult.writeToZip();
+    compileResult
+        .run(parameters.getRuntime(), PKG_NAME + ".MainKt0")
+        .assertSuccessWithOutputLines("3")
+        .inspect(
+            codeInspector -> {
+              final List<FoundClassSubject> lambdaGroups =
+                  codeInspector.allClasses().stream()
+                      .filter(c -> c.getFinalName().contains("LambdaGroup"))
+                      .collect(Collectors.toList());
+              assertEquals(1, lambdaGroups.size());
+            });
+    Path oatFile = temp.newFile("out.oat").toPath();
+    ProcessResult processResult =
+        ToolHelper.runDex2OatRaw(path, oatFile, parameters.getRuntime().asDex().getVm());
+    assertEquals(0, processResult.exitCode);
+    assertThat(processResult.stderr, containsString("Method exceeds compiler instruction limit"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Main.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Main.kt
new file mode 100644
index 0000000..0bbca3c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Main.kt
@@ -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.kotlin.lambda.b159688129
+
+import com.android.tools.r8.NeverInline
+
+fun main() {
+  run ({ arg -> println(arg)}, 3)
+}
+
+@NeverInline
+fun run(param: Function1<Int, Unit>, arg : Int) {
+  param.invoke(arg)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKt$main$1Dump.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKt$main$1Dump.java
new file mode 100644
index 0000000..f9ead12
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKt$main$1Dump.java
@@ -0,0 +1,160 @@
+// 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.kotlin.lambda.b159688129;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+class MainKt$main$1Dump implements Opcodes {
+
+  public static byte[] dump(int mainId, int lambdaId) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(
+        V1_8,
+        ACC_FINAL | ACC_SUPER,
+        "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + mainId + "$main$" + lambdaId,
+        "Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function1<Ljava/lang/Integer;Lkotlin/Unit;>;",
+        "kotlin/jvm/internal/Lambda",
+        new String[] {"kotlin/jvm/functions/Function1"});
+
+    classWriter.visitOuterClass(
+        "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + mainId,
+        lambdaId > 0 ? "main" + lambdaId : "main",
+        "()V");
+
+    {
+      annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+      annotationVisitor0.visit("mv", new int[] {1, 1, 16});
+      annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+      annotationVisitor0.visit("k", new Integer(3));
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+        annotationVisitor1.visit(
+            null,
+            "\u0000\u000e\n"
+                + "\u0000\n"
+                + "\u0002\u0010\u0002\n"
+                + "\u0000\n"
+                + "\u0002\u0010\u0008\n"
+                + "\u0000\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003H\n"
+                + "\u00a2\u0006\u0002\u0008\u0004");
+        annotationVisitor1.visitEnd();
+      }
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+        annotationVisitor1.visit(null, "<anonymous>");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visit(null, "arg");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visit(null, "invoke");
+        annotationVisitor1.visitEnd();
+      }
+      annotationVisitor0.visitEnd();
+    }
+    classWriter.visitInnerClass(
+        "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + mainId + "$main$" + lambdaId,
+        null,
+        null,
+        ACC_FINAL | ACC_STATIC);
+    {
+      fieldVisitor =
+          classWriter.visitField(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC,
+              "INSTANCE",
+              "Lcom/android/tools/r8/kotlin/lambda/b159688129/MainKt"
+                  + mainId
+                  + "$main$"
+                  + lambdaId
+                  + ";",
+              null,
+              null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC,
+              "invoke",
+              "(Ljava/lang/Object;)Ljava/lang/Object;",
+              null,
+              null);
+      methodVisitor.visitCode();
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Number");
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + mainId + "$main$" + lambdaId,
+          "invoke",
+          "(I)V",
+          false);
+      methodVisitor.visitFieldInsn(GETSTATIC, "kotlin/Unit", "INSTANCE", "Lkotlin/Unit;");
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitMaxs(2, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, "invoke", "(I)V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitInsn(ICONST_0);
+      methodVisitor.visitVarInsn(ISTORE, 2);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "kotlin/jvm/internal/Lambda", "<init>", "(I)V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitTypeInsn(
+          NEW,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + mainId + "$main$" + lambdaId);
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + mainId + "$main$" + lambdaId,
+          "<init>",
+          "()V",
+          false);
+      methodVisitor.visitFieldInsn(
+          PUTSTATIC,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + mainId + "$main$" + lambdaId,
+          "INSTANCE",
+          "Lcom/android/tools/r8/kotlin/lambda/b159688129/MainKt"
+              + mainId
+              + "$main$"
+              + lambdaId
+              + ";");
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKtDump.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKtDump.java
new file mode 100644
index 0000000..7321fcb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKtDump.java
@@ -0,0 +1,153 @@
+// 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.kotlin.lambda.b159688129;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class MainKtDump implements Opcodes {
+
+  public static byte[] dump(int id, int numberOfLambdas) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_FINAL | ACC_SUPER,
+        "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + id,
+        null,
+        "java/lang/Object",
+        null);
+
+    {
+      annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+      annotationVisitor0.visit("mv", new int[] {1, 1, 16});
+      annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+      annotationVisitor0.visit("k", new Integer(2));
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+        annotationVisitor1.visit(
+            null,
+            "\u0000\u0016\n"
+                + "\u0000\n"
+                + "\u0002\u0010\u0002\n"
+                + "\u0002\u0008\u0002\n"
+                + "\u0002\u0018\u0002\n"
+                + "\u0002\u0010\u0008\n"
+                + "\u0002\u0008\u0002\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u001a$\u0010\u0002\u001a\u00020\u00012\u0012\u0010\u0003\u001a\u000e\u0012\u0004\u0012\u00020\u0005\u0012\u0004\u0012\u00020\u00010\u00042\u0006\u0010\u0006\u001a\u00020\u0005H\u0007\u00a8\u0006\u0007");
+        annotationVisitor1.visitEnd();
+      }
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+        annotationVisitor1.visit(null, "main");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visit(null, "run");
+        annotationVisitor1.visit(null, "param");
+        annotationVisitor1.visit(null, "Lkotlin/Function1;");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visit(null, "arg");
+        annotationVisitor1.visit(null, "r8.main");
+        annotationVisitor1.visitEnd();
+      }
+      annotationVisitor0.visitEnd();
+    }
+    for (int lambdaId = 0; lambdaId < numberOfLambdas; lambdaId++) {
+      if (lambdaId > 0) {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "main" + lambdaId, "()V", null, null);
+      } else {
+        methodVisitor =
+            classWriter.visitMethod(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "main", "()V", null, null);
+      }
+      methodVisitor.visitCode();
+      methodVisitor.visitFieldInsn(
+          GETSTATIC,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + id + "$main$" + lambdaId,
+          "INSTANCE",
+          "Lcom/android/tools/r8/kotlin/lambda/b159688129/MainKt" + id + "$main$" + lambdaId + ";");
+      methodVisitor.visitTypeInsn(CHECKCAST, "kotlin/jvm/functions/Function1");
+      methodVisitor.visitInsn(ICONST_3);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + id,
+          "run",
+          "(Lkotlin/jvm/functions/Function1;I)V",
+          false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC,
+              "main",
+              "([Ljava/lang/String;)V",
+              null,
+              null);
+      methodVisitor.visitCode();
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + id,
+          "main",
+          "()V",
+          false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(0, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_FINAL | ACC_STATIC,
+              "run",
+              "(Lkotlin/jvm/functions/Function1;I)V",
+              "(Lkotlin/jvm/functions/Function1<-Ljava/lang/Integer;Lkotlin/Unit;>;I)V",
+              null);
+      {
+        annotationVisitor0 =
+            methodVisitor.visitAnnotation("Lcom/android/tools/r8/NeverInline;", true);
+        annotationVisitor0.visitEnd();
+      }
+      methodVisitor.visitAnnotableParameterCount(2, false);
+      {
+        annotationVisitor0 =
+            methodVisitor.visitParameterAnnotation(0, "Lorg/jetbrains/annotations/NotNull;", false);
+        annotationVisitor0.visitEnd();
+      }
+      methodVisitor.visitCode();
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitLdcInsn("param");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "kotlin/jvm/internal/Intrinsics",
+          "checkParameterIsNotNull",
+          "(Ljava/lang/Object;Ljava/lang/String;)V",
+          false);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEINTERFACE,
+          "kotlin/jvm/functions/Function1",
+          "invoke",
+          "(Ljava/lang/Object;)Ljava/lang/Object;",
+          true);
+      methodVisitor.visitInsn(POP);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Test.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Test.kt
new file mode 100644
index 0000000..b213076
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Test.kt
@@ -0,0 +1,9 @@
+// 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.kotlin.lambda.b159688129
+
+fun test() {
+  run({  })
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/TestKtDump.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/TestKtDump.java
new file mode 100644
index 0000000..cf66c3e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/TestKtDump.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.kotlin.lambda.b159688129;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class TestKtDump implements Opcodes {
+
+  public static byte[] dump(int id) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_FINAL | ACC_SUPER,
+        "com/android/tools/r8/kotlin/lambda/b159688129/TestKt" + id,
+        null,
+        "java/lang/Object",
+        null);
+
+    {
+      annotationVisitor0 = classWriter.visitAnnotation("Lkotlin/Metadata;", true);
+      annotationVisitor0.visit("mv", new int[] {1, 1, 16});
+      annotationVisitor0.visit("bv", new int[] {1, 0, 3});
+      annotationVisitor0.visit("k", new Integer(2));
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d1");
+        annotationVisitor1.visit(
+            null,
+            "\u0000\u0008\n"
+                + "\u0000\n"
+                + "\u0002\u0010\u0002\n"
+                + "\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u00a8\u0006\u0002");
+        annotationVisitor1.visitEnd();
+      }
+      {
+        AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
+        annotationVisitor1.visit(null, "test");
+        annotationVisitor1.visit(null, "");
+        annotationVisitor1.visit(null, "r8.main");
+        annotationVisitor1.visitEnd();
+      }
+      annotationVisitor0.visitEnd();
+    }
+    classWriter.visitInnerClass(
+        "com/android/tools/r8/kotlin/lambda/b159688129/TestKt" + id + "$test$1",
+        null,
+        null,
+        ACC_FINAL | ACC_STATIC);
+
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "test", "()V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitFieldInsn(
+          GETSTATIC,
+          "com/android/tools/r8/kotlin/lambda/b159688129/TestKt" + id + "$test$1",
+          "INSTANCE",
+          "Lcom/android/tools/r8/kotlin/lambda/b159688129/TestKt" + id + "$test$1;");
+      methodVisitor.visitTypeInsn(CHECKCAST, "kotlin/jvm/functions/Function0");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/kotlin/lambda/b159688129/MainKt" + id,
+          "run",
+          "(Lkotlin/jvm/functions/Function0;)V",
+          false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java
index ba40c07..4095713 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IllegalFieldRebindingTest.java
@@ -49,7 +49,7 @@
             .addInnerClasses(IllegalFieldRebindingTest.class)
             .addInnerClasses(IllegalFieldRebindingTestClasses.class)
             .addKeepMainRule(TestClass.class)
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
@@ -81,7 +81,7 @@
             .addInnerClasses(IllegalFieldRebindingTest.class)
             .addInnerClasses(IllegalFieldRebindingTestClasses.class)
             .addKeepMainRule(OtherTestClass.class)
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .run(OtherTestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
index 77a9ea9..aa38f06 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
@@ -43,7 +43,7 @@
             MemberRebindingBridgeRemovalTest.class, MemberRebindingBridgeRemovalTestClasses.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
index b1ae543..9d18387 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.memberrebinding;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -48,7 +48,7 @@
         .addInnerClasses(MemberRebindingConflictTestClasses.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -65,7 +65,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
@@ -74,7 +74,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class B extends A {
 
     // public synthetic void foo() { super.foo(); }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java
index 64b4d1f..aaba050 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/IllegalFieldRebindingTestClasses.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.memberrebinding.testclasses;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
 public class IllegalFieldRebindingTestClasses {
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
     public static int f;
   }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingBridgeRemovalTestClasses.java b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingBridgeRemovalTestClasses.java
index 3e15e6e..12ebac3 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingBridgeRemovalTestClasses.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingBridgeRemovalTestClasses.java
@@ -5,11 +5,11 @@
 package com.android.tools.r8.memberrebinding.testclasses;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
 public class MemberRebindingBridgeRemovalTestClasses {
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
index 180541a..7d0d809 100644
--- a/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
@@ -10,8 +10,8 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -33,7 +33,7 @@
             .enableMemberValuePropagationAnnotations()
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
@@ -54,7 +54,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverPropagateValue public String f1;
@@ -64,7 +64,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A {
 
     @NeverPropagateValue public String f2;
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
index 4d43352..ca294fa 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceFieldMinificationTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.naming.testclasses.Greeting;
 import com.android.tools.r8.utils.StringUtils;
@@ -25,7 +25,7 @@
         .addKeepRules("-keep,allowobfuscation class " + Tag.class.getTypeName() + " { <fields>; }")
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .run(TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
   }
@@ -50,7 +50,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface Tag {
 
     String TAG = "Greeter";
diff --git a/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java b/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
index 9a55cee..efb5d35 100644
--- a/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,7 +44,7 @@
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
     testForR8(parameters.getBackend())
         .addInnerClasses(IntersectionLambdaTest.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -52,7 +52,7 @@
   }
 
   @FunctionalInterface
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 5b26d4f..93a0639 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -15,6 +15,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.kotlin.TestKotlinClass;
+import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
+import com.android.tools.r8.shaking.NoStaticClassMergingRule;
+import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -130,7 +133,9 @@
             .addKeepRules(
                 StringUtils.lines(
                     "-neverclassinline class **." + targetClassName,
-                    "-nevermerge class **." + targetClassName,
+                    "-" + NoVerticalClassMergingRule.RULE_NAME + " class **." + targetClassName,
+                    "-" + NoHorizontalClassMergingRule.RULE_NAME + " class **." + targetClassName,
+                    "-" + NoStaticClassMergingRule.RULE_NAME + " class **." + targetClassName,
                     "-neverinline class **." + targetClassName + " { <methods>; }"))
             .allowDiagnosticWarningMessages()
             .minification(minification)
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java
index e93b0fa..c4b3181 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInInterfacesWithCommonSubClassTest.java
@@ -10,7 +10,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -45,7 +45,7 @@
     CodeInspector inspector =
         testForR8(Backend.DEX)
             .addProgramClasses(TestClass.class, A.class, B.class, I.class, J.class)
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
                 reserveName
@@ -85,22 +85,22 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     String f1 = System.currentTimeMillis() >= 0 ? "Hello " : null;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J {
 
     String a = System.currentTimeMillis() >= 0 ? "world!" : null;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A implements I {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A implements J {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
index 67b86ce..38bcbad 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
@@ -9,8 +9,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -52,7 +52,7 @@
             .addProgramClasses(
                 TestClass.class, A.class, B.class, C.class, I.class, J.class, K.class)
             .enableMemberValuePropagationAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
                 reserveName
@@ -140,31 +140,31 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverPropagateValue String f1 = "He";
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A {
 
     @NeverPropagateValue String f2 = "l";
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     String f3 = System.currentTimeMillis() >= 0 ? "lo" : null;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J extends I {
 
     String f4 = System.currentTimeMillis() >= 0 ? " " : null;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface K {
 
     String f5 = System.currentTimeMillis() >= 0 ? "world" : null;
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
index 89804e8..65c62b8 100644
--- a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
@@ -10,8 +10,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
 
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -48,7 +48,7 @@
         testForR8(Backend.DEX)
             .addProgramClasses(TestClass.class, A.class, B.class, I.class, J.class)
             .enableMemberValuePropagationAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
                 reserveName
@@ -105,7 +105,7 @@
         .addLibraryClasses(I.class, J.class)
         .addLibraryFiles(runtimeJar(Backend.DEX))
         .enableMemberValuePropagationAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(TestClass.class)
         .compile()
         .addRunClasspathFiles(
@@ -126,19 +126,19 @@
     assertEquals(expectedNameForF2, f2FieldSubject.getFinalName());
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     String f1 = System.currentTimeMillis() >= 0 ? "Hello" : null;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J extends I {
 
     String a = System.currentTimeMillis() >= 0 ? "world!" : null;
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverPropagateValue String f2 = " ";
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
index 7fa0543..4d000bb 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
@@ -10,8 +10,8 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -81,7 +81,7 @@
   }
 
   private static final Class<?>[] LIBRARY_CLASSES = {
-    NeverMerge.class, LibraryBase.class, LibrarySubclass.class
+    NoVerticalClassMerging.class, LibraryBase.class, LibrarySubclass.class
   };
 
   private static final Class<?>[] PROGRAM_CLASSES = {
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
index 725c5a9..cbd6e3b 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
@@ -8,8 +8,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -27,7 +27,7 @@
 import org.junit.runners.Parameterized;
 
 // AbstractChecker -> X:
-@NeverMerge
+@NoVerticalClassMerging
 abstract class AbstractChecker {
   // String tag -> p
   @NeverPropagateValue private String tag = "PrivateInitialTag_AbstractChecker";
@@ -123,7 +123,7 @@
             .addKeepMainRule(MemberResolutionTestMain.class)
             .addKeepRules("-applymapping " + mapPath)
             .enableMemberValuePropagationAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .addOptionsModification(options -> options.enableInlining = false)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MemberResolutionTestMain.class)
diff --git a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
index 5da3af5..66b2a18 100644
--- a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
@@ -24,10 +24,10 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-@NeverMerge
+@NoStaticClassMerging
 class Outer {
 
-  @NeverMerge
+  @NoStaticClassMerging
   static class Inner {
     @NeverInline
     static void foo() {
@@ -80,7 +80,7 @@
             // the visiting of classes during class minification to be Outer$Inner before Outer.
             "-keepnames class " + Outer.class.getCanonicalName() + "$Inner",
             keepOuterName ? "-keepnames class " + Outer.class.getCanonicalName() : "",
-            "-nevermerge @com.android.tools.r8.NeverMerge class *"),
+            noStaticClassMergingRule()),
         Origin.unknown());
 
     ToolHelper.allowTestProguardOptions(builder);
diff --git a/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java b/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java
index 56ed80c..0add817 100644
--- a/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java
+++ b/src/test/java/com/android/tools/r8/naming/b128656974/B128656974.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.naming.testclasses.Greeting;
 import com.android.tools.r8.utils.StringUtils;
@@ -27,7 +27,7 @@
     testForR8(Backend.DEX)
         .addProgramClasses(Greeting.class, Greeting.getGreetingBase(), TestClassSub.class, main)
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(main)
         .addKeepRules(
             "-keepclassmembernames class "
@@ -74,7 +74,7 @@
     Class<?> main = TestClassMainForMethod.class;
     testForR8(Backend.DEX)
         .addProgramClasses(TestClassBase.class, TestClassSub2.class, main)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addKeepMainRule(main)
@@ -92,7 +92,7 @@
             });
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class TestClassBase {
     @NeverInline
     void foo() {
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/Greeting.java b/src/test/java/com/android/tools/r8/naming/testclasses/Greeting.java
index 221301b..20f6df1 100644
--- a/src/test/java/com/android/tools/r8/naming/testclasses/Greeting.java
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/Greeting.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.naming.testclasses;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 
-@NeverMerge
+@NoVerticalClassMerging
 public class Greeting extends GreetingBase {
 
   public static Class<?> getGreetingBase() {
@@ -14,7 +14,7 @@
   }
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class GreetingBase {
 
   protected String greeting;
diff --git a/src/test/java/com/android/tools/r8/repackage/CrossPackageInvokeSuperToPackagePrivateMethodTest.java b/src/test/java/com/android/tools/r8/repackage/CrossPackageInvokeSuperToPackagePrivateMethodTest.java
index 32a8cc8..4e4001a 100644
--- a/src/test/java/com/android/tools/r8/repackage/CrossPackageInvokeSuperToPackagePrivateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/CrossPackageInvokeSuperToPackagePrivateMethodTest.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestBuilder;
@@ -53,7 +53,7 @@
         .apply(this::addProgramClasses)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -112,7 +112,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class A {
 
     @NeverInline
@@ -122,7 +122,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class B extends A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
index 1639c79..8e08452 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
@@ -4,10 +4,11 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 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.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -25,8 +26,6 @@
 @RunWith(Parameterized.class)
 public class RepackageAfterCollisionWithPackagePrivateSignatureTest extends TestBase {
 
-  private static final String FLATTEN_PACKAGE_HIERARCHY = "flattenpackagehierarchy";
-  private static final String REPACKAGE_CLASSES = "repackageclasses";
   private static final String REPACKAGE_DIR = "foo";
 
   private final String flattenPackageHierarchyOrRepackageClasses;
@@ -76,7 +75,7 @@
 
     public static void main(String[] args) {
       RepackageCandidate.foo(0);
-      RepackageCandidate.foo((int) System.currentTimeMillis(), 0);
+      RepackageCandidate.foo(System.currentTimeMillis(), 0);
     }
 
     static void restrictToCurrentPackage() {
@@ -86,12 +85,12 @@
 
   public static class RepackageCandidate {
 
-    public static void foo(int unused) {
+    public static void foo(long unused) {
       TestClass.restrictToCurrentPackage();
     }
 
     @NeverInline
-    public static void foo(int used, int unused) {
+    public static void foo(long used, int unused) {
       if (used >= 0) {
         System.out.println(" world!");
       }
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 c656981..1d9718b 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -129,7 +129,7 @@
             options ->
                 options.testing.enableExperimentalRepackaging = enableExperimentalRepackaging)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoStaticClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
new file mode 100644
index 0000000..40e7721
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
@@ -0,0 +1,111 @@
+// 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.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+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 com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithFeatureSplitTest extends TestBase {
+
+  private static final String REPACKAGE_DIR = "foo";
+
+  private final String flattenPackageHierarchyOrRepackageClasses;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public RepackageWithFeatureSplitTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(BaseClass.class)
+        .addFeatureSplit(FeatureMain.class, FeatureClass.class)
+        .addFeatureSplitRuntime()
+        .addKeepFeatureMainRule(FeatureMain.class)
+        .addKeepRules(
+            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
+        .addOptionsModification(
+            options -> {
+              assertFalse(options.testing.enableExperimentalRepackaging);
+              options.testing.enableExperimentalRepackaging = true;
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspectBase, this::inspectFeature)
+        .runFeature(parameters.getRuntime(), FeatureMain.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspectBase(CodeInspector inspector) {
+    assertEquals(3, inspector.allClasses().size());
+
+    // The base classes added here.
+    assertThat(inspector.clazz(BaseClass.class), isPresent());
+
+    // The feature split runtime.
+    assertThat(inspector.clazz(RunInterface.class), isPresent());
+    assertThat(inspector.clazz(SplitRunner.class), isPresent());
+  }
+
+  private void inspectFeature(CodeInspector inspector) {
+    assertEquals(2, inspector.allClasses().size());
+    assertThat(inspector.clazz(FeatureMain.class), isPresent());
+    assertThat(inspector.clazz(FeatureClass.class), isPresent());
+  }
+
+  public static class BaseClass {
+
+    @NeverInline
+    public static void hello() {
+      System.out.print("Hello");
+    }
+  }
+
+  public static class FeatureMain implements RunInterface {
+
+    @Override
+    public void run() {
+      BaseClass.hello();
+      FeatureClass.world();
+    }
+  }
+
+  public static class FeatureClass {
+
+    @NeverInline
+    public static void world() {
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
new file mode 100644
index 0000000..501340a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
@@ -0,0 +1,98 @@
+// 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.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithInitClassTest extends TestBase {
+
+  private static final String REPACKAGE_PACKAGE = "foo";
+
+  private final String flattenPackageHierarchyOrRepackageClasses;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public RepackageWithInitClassTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addClassObfuscationDictionary("a")
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_PACKAGE + "\"")
+        .addOptionsModification(
+            options -> {
+              assert !options.testing.enableExperimentalRepackaging;
+              options.testing.enableExperimentalRepackaging = true;
+            })
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject repackagedClassSubject = inspector.clazz(StaticMemberValuePropagation.class);
+    assertThat(repackagedClassSubject, isPresent());
+
+    // Verify that a $r8$clinit field was synthesized.
+    String clinitFieldName = inspector.getFactory().objectMembers.clinitField.name.toSourceString();
+    assertThat(repackagedClassSubject.uniqueFieldWithName(clinitFieldName), isPresent());
+    assertThat(repackagedClassSubject.uniqueFieldWithName("GREETING"), not(isPresent()));
+
+    // Verify that the class was repackaged.
+    assertEquals(
+        flattenPackageHierarchyOrRepackageClasses.equals(FLATTEN_PACKAGE_HIERARCHY)
+            ? REPACKAGE_PACKAGE + ".a"
+            : REPACKAGE_PACKAGE,
+        repackagedClassSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(StaticMemberValuePropagation.GREETING);
+    }
+  }
+
+  public static class StaticMemberValuePropagation {
+
+    public static String GREETING = " world!";
+
+    static {
+      System.out.print("Hello");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundFieldReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundFieldReferenceTest.java
new file mode 100644
index 0000000..c8a9d82
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundFieldReferenceTest.java
@@ -0,0 +1,77 @@
+// 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.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.repackage.RepackageWithMainDexListTest.TestClass;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundFieldReferenceTestClasses;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundFieldReferenceTestClasses.B;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackagingWithNonReboundFieldReferenceTest extends TestBase {
+
+  private static final String REPACKAGE_DIR = "foo";
+
+  private final String flattenPackageHierarchyOrRepackageClasses;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public RepackagingWithNonReboundFieldReferenceTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addInnerClasses(getClass(), RepackagingWithNonReboundFieldReferenceTestClasses.class)
+          .addKeepMainRule(TestClass.class)
+          .addKeepRules(
+              "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
+          .addOptionsModification(
+              options -> {
+                assertFalse(options.testing.enableExperimentalRepackaging);
+                options.testing.enableExperimentalRepackaging = true;
+              })
+          .enableMemberValuePropagationAnnotations()
+          .enableNoVerticalClassMergingAnnotations()
+          .setMinApi(parameters.getApiLevel())
+          .compile();
+
+      // TODO(b/168282032): Support lens rewriting of non-rebound references in the writer.
+      fail();
+    } catch (CompilationFailedException exception) {
+      // Ignore.
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(B.GREETING);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundMethodReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundMethodReferenceTest.java
new file mode 100644
index 0000000..48891e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundMethodReferenceTest.java
@@ -0,0 +1,77 @@
+// 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.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundMethodReferenceTestClasses;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundMethodReferenceTestClasses.B;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackagingWithNonReboundMethodReferenceTest extends TestBase {
+
+  private static final String REPACKAGE_DIR = "foo";
+
+  private final String flattenPackageHierarchyOrRepackageClasses;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public RepackagingWithNonReboundMethodReferenceTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addInnerClasses(getClass(), RepackagingWithNonReboundMethodReferenceTestClasses.class)
+          .addKeepMainRule(TestClass.class)
+          .addKeepRules(
+              "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
+          .addOptionsModification(
+              options -> {
+                assertFalse(options.testing.enableExperimentalRepackaging);
+                options.testing.enableExperimentalRepackaging = true;
+              })
+          .enableInliningAnnotations()
+          .enableNeverClassInliningAnnotations()
+          .enableNoVerticalClassMergingAnnotations()
+          .setMinApi(parameters.getApiLevel())
+          .compile();
+
+      // TODO(b/168282032): Support lens rewriting of non-rebound references in the writer.
+      fail();
+    } catch (CompilationFailedException exception) {
+      // Ignore.
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B().greet();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.java b/src/test/java/com/android/tools/r8/repackage/testclasses/CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.java
index fc9639a..c9c8690 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/CrossPackageInvokeSuperToPackagePrivateMethodTestClasses.java
@@ -6,13 +6,13 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.repackage.CrossPackageInvokeSuperToPackagePrivateMethodTest;
 
 public class CrossPackageInvokeSuperToPackagePrivateMethodTestClasses {
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class C extends CrossPackageInvokeSuperToPackagePrivateMethodTest.B {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundFieldReferenceTestClasses.java b/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundFieldReferenceTestClasses.java
new file mode 100644
index 0000000..b33496f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundFieldReferenceTestClasses.java
@@ -0,0 +1,19 @@
+// 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.repackage.testclasses;
+
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
+
+public class RepackagingWithNonReboundFieldReferenceTestClasses {
+
+  @NoVerticalClassMerging
+  static class A {
+
+    @NeverPropagateValue public static String GREETING = "Hello world!";
+  }
+
+  public static class B extends A {}
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundMethodReferenceTestClasses.java b/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundMethodReferenceTestClasses.java
new file mode 100644
index 0000000..fb4775f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundMethodReferenceTestClasses.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+
+public class RepackagingWithNonReboundMethodReferenceTestClasses {
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  static class A {
+
+    @NeverInline
+    public void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+
+  public static class B extends A {}
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
index 8089eb2..4f47751 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
index c7c3695..fdc4a9c 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect {
 
   @NeverInline
@@ -15,7 +15,7 @@
     Helper.test();
   }
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
index 6948b55..0f9ae96 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodOnReachableClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
index 7d5baae..68aa391 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodOnReachableClassIndirect {
 
   @NeverInline
@@ -15,7 +15,7 @@
     Helper.test();
   }
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
index f3a90e6..de0e859 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
index ddfd95b..13fef6e 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect {
 
   @NeverInline
@@ -15,7 +15,7 @@
     Helper.test();
   }
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
index 11110bd..fc92f85 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
index 0b737ff..c30cd59 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
@@ -5,9 +5,10 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassIndirect {
 
   @NeverInline
@@ -15,7 +16,7 @@
     Helper.test();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
index f9a5b76..73f2d6e 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnReachableClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
index 2c0a1b2..54cbea9 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnReachableClassIndirect {
 
   @NeverInline
@@ -15,7 +15,7 @@
     Helper.test();
   }
 
-  @NeverMerge
+  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
index 0f54397..cfb5aa4 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPublicKeptMethodAllowRenamingOnReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
index dcd6aa4..75d353c 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPublicKeptMethodOnReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
index f6cf98a..b77f419 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPublicMethodOnKeptClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
index 05f943c..f71b585 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPublicMethodOnKeptClassAllowRenaming {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
index 0c21cef..87ed71f 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class AccessPublicMethodOnReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
index 57b2d65..10f12bf 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
@@ -5,9 +5,9 @@
 package com.android.tools.r8.repackage.testclasses.repackagetest;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class ReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index 440e077..3215f17 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -85,7 +85,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(DefaultMethodAsOverrideWithLambdaTest.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -94,7 +94,7 @@
   }
 
   @FunctionalInterface
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
 
@@ -103,7 +103,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J extends I {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index edbb580..77fa9d8 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -81,7 +81,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(DefaultMethodLambdaTest.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -90,7 +90,7 @@
   }
 
   @FunctionalInterface
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
 
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index 8d2a599..70a350f 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -144,12 +144,12 @@
     return transformer(A.class).setImplements(I.class, K.class).transform();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J {
     @NeverInline
     default void foo() {
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
index ef5656d..8b1f615 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -82,7 +82,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(DuplicateImportsTest.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -90,12 +90,12 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J extends I {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 0702c97..5422f9b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -82,7 +82,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(LambdaMultipleInterfacesTest.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -91,7 +91,7 @@
   }
 
   @FunctionalInterface
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
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 5ad3690..eb88c69 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
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -82,7 +82,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(MultipleImplementsTest.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -90,17 +90,17 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J {
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public abstract static class A implements I, J {}
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
index 4d195e1..ec16d6d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -89,7 +89,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(SimpleInterfaceInvokeTest.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -97,7 +97,7 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
index 51727c7..3e582c5 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -12,7 +12,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -88,7 +88,7 @@
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
     testForR8(parameters.getBackend())
         .addInnerClasses(SubInterfaceOverridesTest.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -97,13 +97,13 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
 
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J extends I {
 
     @Override
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 9c02be6..5628ea2 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
@@ -12,7 +12,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -87,7 +87,7 @@
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
     testForR8(parameters.getBackend())
         .addInnerClasses(SubTypeMissingOverridesTest.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -96,7 +96,7 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
 
     void foo();
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
index d5d1ce8..65d8bca 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -12,7 +12,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -87,7 +87,7 @@
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
     testForR8(parameters.getBackend())
         .addInnerClasses(SubTypeOverridesTest.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -96,7 +96,7 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
 
     void foo();
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
index 7f52044..787f992 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -83,7 +83,7 @@
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
     testForR8(parameters.getBackend())
         .addInnerClasses(AbstractInMiddleTest.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
@@ -92,7 +92,7 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   public static class A {
 
@@ -102,14 +102,14 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public abstract static class B extends A {
 
     @Override
     public abstract void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   public static class C extends B {
 
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index 6264f35..80b6908 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -83,18 +83,18 @@
         .addKeepMainRule(Main.class)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J extends I {
     @Override
     @NeverInline
@@ -103,7 +103,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public abstract static class A implements I {}
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 8f818b2..a4966b4 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -83,18 +83,18 @@
         .addKeepMainRule(Main.class)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J extends I {
     @Override
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
index 2b236b5..0daaa44 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -95,12 +95,12 @@
     return transformer(A.class).setImplements(I.class, J.class).transform();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     void foo();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface J {
     @NeverInline
     default void foo() {
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index 9c7245c..dcb03f9 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -82,13 +82,13 @@
         .addKeepMainRule(Main.class)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
 
     @NeverInline
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 fbf8ac5..c1f2301 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
@@ -13,7 +13,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -89,7 +89,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(TargetInDefaultMethodTest.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -97,7 +97,7 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public interface I {
     @NeverInline
     default void foo() {
@@ -105,7 +105,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public abstract static class A implements I {}
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/shaking/B149831282.java b/src/test/java/com/android/tools/r8/shaking/B149831282.java
index 97441cc..67b9bca 100644
--- a/src/test/java/com/android/tools/r8/shaking/B149831282.java
+++ b/src/test/java/com/android/tools/r8/shaking/B149831282.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestBuilder;
 import com.android.tools.r8.TestParameters;
@@ -46,7 +46,7 @@
         .apply(this::addProgramInputs)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -84,7 +84,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @NeverInline
@@ -93,7 +93,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class B extends A {}
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
index 66c8152..745e5b7 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -11,7 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,7 +53,7 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(
@@ -154,7 +154,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   static class InstanceFieldWithInitialization_Z {
     boolean alwaysFalse;
     InstanceFieldWithInitialization_Z() {
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalStaticFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalStaticFieldsTest.java
index 302cb28..35cd9c9 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalStaticFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalStaticFieldsTest.java
@@ -11,7 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,7 +53,7 @@
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(
@@ -147,7 +147,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   static class StaticFieldWithInitialization_Z {
     static boolean alwaysFalse;
     @NeverInline
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 741496d..382f515 100644
--- a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -39,7 +39,7 @@
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addInnerClasses(EventuallyNonTargetedMethodTest.class)
         .addKeepMainRule(Main.class)
@@ -56,7 +56,7 @@
     assertThat(classSubject.uniqueMethodWithName("foo"), isPresent());
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   private static class A {
     @NeverInline
     public void foo() {
@@ -64,7 +64,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   private static class B extends A {
     // No override of foo, but B::foo will be the only target.
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
index bff8192..437cdd2 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
@@ -6,7 +6,7 @@
 
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -40,7 +40,7 @@
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(
             options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("true", "true");
@@ -79,7 +79,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   abstract static class A extends AbstractList<Object> {
 
     @Override
@@ -98,13 +98,13 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     boolean isEmpty();
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A implements I {
     // Intentionally empty.
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
index 9d16769..7d68070 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
@@ -6,7 +6,7 @@
 
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,7 +43,7 @@
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(
             options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("null", "null");
@@ -88,14 +88,14 @@
   }
 
   @FunctionalInterface
-  @NeverMerge
+  @NoVerticalClassMerging
   interface I {
 
     Iterator<Object> iterator();
   }
 
   @FunctionalInterface
-  @NeverMerge
+  @NoVerticalClassMerging
   interface J extends Iterable<Object> {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
index df8fdc4..a7908c2 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideMarkingTest.java
@@ -8,7 +8,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,7 +41,7 @@
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(
             options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile();
   }
@@ -69,7 +69,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     @Override
@@ -78,7 +78,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class B extends A {
 
     @Override
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 8f560a2..f813bf2 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonTargetedMethodTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -40,7 +40,7 @@
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .addInnerClasses(NonTargetedMethodTest.class)
         .addKeepMainRule(Main.class)
@@ -56,7 +56,7 @@
     assertThat(classSubject.uniqueMethodWithName("foo"), not(isPresent()));
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   private static class A {
     @NeverInline
     public void foo() {
@@ -64,7 +64,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   @NeverClassInline
   private static class B extends A {
     // No override of foo, but B::foo will be the only target.
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
index f4917c7..c4d9068 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.shaking.annotations;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -54,7 +54,7 @@
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-keepattributes *Annotation*", "-dontobfuscate")
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
@@ -83,7 +83,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface Interface {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
index 9c38214..8462593 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,7 +43,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules(
@@ -76,7 +76,7 @@
     void debug(String tag, String message);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class BaseImplementer implements LoggerInterface {
     @Override
     public void debug(String message) {
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
index 71614d2..6b053ee 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -157,7 +157,7 @@
         .addKeepMainRule(MAIN)
         .addKeepRules(config.getKeepRule())
         .noMinification()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -178,7 +178,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   static class ProgramBase extends LibraryBase {
     @NeverInline
     @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
index 34b1b33..daaacb2 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
@@ -10,7 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.errors.Unreachable;
@@ -93,7 +93,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(AssumenosideeffectsWithMultipleTargetsTest.class)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addKeepMainRule(MAIN)
@@ -109,9 +109,8 @@
     void info(String tag, String message);
   }
 
-  @NeverMerge
-  static abstract class AbstractLogger implements TestLogger {
-  }
+  @NoVerticalClassMerging
+  abstract static class AbstractLogger implements TestLogger {}
 
   @NeverClassInline
   static class TestLoggerImplementer implements TestLogger {
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
index 27bb092..c2be7a8 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
@@ -5,7 +5,7 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
@@ -17,7 +17,7 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-@NeverMerge
+@NoVerticalClassMerging
 interface I {
   static void foo() {
     System.out.println("static::foo");
@@ -61,7 +61,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
         .setMinApi(parameters.getApiLevel())
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { <methods>; }")
         .compile()
@@ -75,7 +75,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
         .setMinApi(parameters.getApiLevel())
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { *(); }")
         .compile()
@@ -89,7 +89,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
         .setMinApi(parameters.getApiLevel())
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { *** f*(); }")
         .compile()
@@ -103,7 +103,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
         .setMinApi(parameters.getApiLevel())
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { static void foo(); }")
         .allowDiagnosticWarningMessages()
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
index 8c782f2..14c99ba 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
@@ -10,7 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.shaking.methods.MethodsTestBase.Shrinker;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -26,32 +26,32 @@
 import java.util.function.Function;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 interface SuperIface {
   default void m1() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 interface SubIface extends SuperIface {
   default void m2() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 interface SubSubIface extends SubIface {
   default void m3() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Impl implements SubSubIface {
   void m4() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubImpl extends Impl {
   void m5() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSubImpl extends SubImpl {
   void m6() {}
 }
@@ -111,7 +111,7 @@
       throws Throwable {
     testForR8(Backend.DEX)
         .setMinApi(AndroidApiLevel.L)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addProgramClasses(getClasses())
         .addKeepRules(keepRules)
         .compile()
@@ -125,7 +125,7 @@
       throws Throwable {
     testForR8Compat(Backend.DEX)
         .setMinApi(AndroidApiLevel.L)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addProgramClasses(getClasses())
         .addKeepRules(keepRules)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
index 5f6a618..a48c5e9 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -7,7 +7,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
@@ -17,7 +17,7 @@
 import java.lang.reflect.Method;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 interface I {
   default void m() {
     System.out.println("I::m");
@@ -45,7 +45,7 @@
     testForR8(Backend.DEX)
         .addProgramClasses(I.class, C.class, MAIN)
         .setMinApi(AndroidApiLevel.L)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { m(); }")
         .run(MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
index 061e91a..c461169 100644
--- a/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking.fields;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -25,7 +25,7 @@
       String expected)
       throws Throwable {
     testForR8(Backend.DEX)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addProgramClasses(getClasses())
         .addKeepRules(keepRules)
         .compile()
@@ -41,7 +41,7 @@
       throws Throwable {
     testForProguard()
         .addProgramClasses(getClasses())
-        .addProgramClasses(NeverMerge.class)
+        .addProgramClasses(NoVerticalClassMerging.class)
         .addKeepRules(keepRules)
         .compile()
         .inspect(inspector)
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/pblc/PublicFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/pblc/PublicFieldsTest.java
index 7ac89e0..f2f68d8 100644
--- a/src/test/java/com/android/tools/r8/shaking/fields/pblc/PublicFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/fields/pblc/PublicFieldsTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.fields.FieldsTestBase;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -17,17 +17,17 @@
 import java.util.Set;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   public int f1;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   public int f2;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   public int f3;
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/pckg/PackageFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/pckg/PackageFieldsTest.java
index 86c22f6..113b862 100644
--- a/src/test/java/com/android/tools/r8/shaking/fields/pckg/PackageFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/fields/pckg/PackageFieldsTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.fields.FieldsTestBase;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -17,17 +17,17 @@
 import java.util.Set;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   int f1;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   int f2;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   int f3;
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/prvt/PrivateFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/prvt/PrivateFieldsTest.java
index 0ceefb4..e70f0a3 100644
--- a/src/test/java/com/android/tools/r8/shaking/fields/prvt/PrivateFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/fields/prvt/PrivateFieldsTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.fields.FieldsTestBase;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -17,17 +17,17 @@
 import java.util.Set;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   private int f1;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   private int f2;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   private int f3;
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/shadow/ShadowFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/fields/shadow/ShadowFieldsTest.java
index 6cc26a6..da756b3 100644
--- a/src/test/java/com/android/tools/r8/shaking/fields/shadow/ShadowFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/fields/shadow/ShadowFieldsTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.fields.FieldsTestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -16,17 +16,17 @@
 import java.util.Collection;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   public int f1;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   public int f1;
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   public int f1;
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
index b79d90e..a969d1b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
@@ -9,14 +9,13 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
 import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -67,7 +66,7 @@
             .allowUnusedProguardConfigurationRules()
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .run(parameters.getRuntime(), TestClass.class)
@@ -104,7 +103,7 @@
   }
 
   @NeverClassInline
-  @NeverMerge
+  @NoVerticalClassMerging
   interface Interface {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersRuleOnIndirectlyInstantiatedClassTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersRuleOnIndirectlyInstantiatedClassTest.java
index 5b47f4f..9dd4b60 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersRuleOnIndirectlyInstantiatedClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersRuleOnIndirectlyInstantiatedClassTest.java
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -45,7 +45,7 @@
             "  java.lang.String greeting;",
             "}")
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(this::verifyFieldIsPresent)
@@ -82,7 +82,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class A {
 
     public String greeting;
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java
index 2ce88f3..528a4ac 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java
@@ -7,7 +7,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
@@ -44,7 +44,7 @@
     GraphInspector inspector =
         testForR8(parameters.getBackend())
             .enableGraphInspector()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .addProgramClasses(CLASS, A.class, B.class)
             .addKeepMainRule(CLASS)
@@ -72,7 +72,7 @@
   }
 
   // Base class, A.foo never resolved to at runtime.
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class A {
 
     @NeverInline
@@ -82,7 +82,7 @@
   }
 
   // Actual and only instantiated type.
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class B extends A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
index 8a67d39..5bb8e48 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
@@ -6,7 +6,7 @@
 import static com.android.tools.r8.references.Reference.methodFromMethod;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -22,7 +22,7 @@
 @RunWith(Parameterized.class)
 public class KeptByTwoMethods extends TestBase {
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class A {
 
     @NeverInline
@@ -31,7 +31,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public static class B extends A {
     // Intermediate to A.
   }
@@ -72,7 +72,7 @@
     GraphInspector inspector =
         testForR8(parameters.getBackend())
             .enableGraphInspector()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .addKeepMainRule(TestClass.class)
             .addProgramClasses(TestClass.class, A.class, B.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
index 5e7786f..12d1f35 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
@@ -7,8 +7,8 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
@@ -47,7 +47,7 @@
             .enableGraphInspector()
             .enableInliningAnnotations()
             .enableMemberValuePropagationAnnotations()
-            .enableMergeAnnotations()
+            .enableNoVerticalClassMergingAnnotations()
             .enableNeverClassInliningAnnotations()
             .addProgramClasses(CLASS, Foo.class, Bar.class)
             .addKeepMainRule(CLASS)
@@ -66,7 +66,7 @@
     barClass.assertPresent().assertKeptBy(fooClass);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   public abstract static class Bar {}
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 366a97e..4d385a0 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -12,7 +12,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -37,7 +36,6 @@
 @RunWith(Parameterized.class)
 public class KeptViaClassInitializerTestRunner extends TestBase {
 
-  @NeverMerge
   @NeverClassInline
   public static class A {
 
@@ -47,7 +45,6 @@
     }
   }
 
-  @NeverMerge
   @NeverClassInline
   public enum T {
     A(A::new);
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 0f8a6bd..bc12cfc 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
@@ -9,7 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -42,7 +42,7 @@
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(options -> options.enableTreeShakingOfLibraryMethodOverrides = true)
         .enableNeverClassInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyOutput)
@@ -95,7 +95,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class EscapesIndirectly {
 
     @Override
@@ -106,7 +106,7 @@
 
   static class EscapesIndirectlySub extends EscapesIndirectly {}
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class EscapesIndirectlyWithOverride {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java b/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
index 7adb908..07be6e7 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/MethodsTestBase.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking.methods;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -30,7 +30,7 @@
       List<String> keepRules, BiConsumer<CodeInspector, Shrinker> inspector, String expected)
       throws Throwable {
     testForR8(Backend.DEX)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addProgramClasses(getClasses())
         .addKeepRules(keepRules)
         .compile()
@@ -43,7 +43,7 @@
       List<String> keepRules, BiConsumer<CodeInspector, Shrinker> inspector, String expected)
       throws Throwable {
     testForR8Compat(Backend.DEX)
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .addProgramClasses(getClasses())
         .addKeepRules(keepRules)
         .compile()
@@ -57,7 +57,7 @@
       throws Throwable {
     testForProguard()
         .addProgramClasses(getClasses())
-        .addProgramClasses(NeverMerge.class)
+        .addProgramClasses(NoVerticalClassMerging.class)
         .addKeepRules(keepRules)
         .compile()
         .inspect(i -> inspector.accept(i, Shrinker.Proguard))
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/abst/AbstractMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/abst/AbstractMethodsTest.java
index 8944bb6..9fe2a68 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/abst/AbstractMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/abst/AbstractMethodsTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.methods.MethodsTestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -20,17 +20,17 @@
 import java.util.Set;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 abstract class Super {
   public abstract void m1();
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 abstract class Sub extends Super {
   public abstract void m2();
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   public void m1() {}
 
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
index ddeff6d..2ce8046 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/pblc/PublicMethodsTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.methods.MethodsTestBase;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -18,17 +18,17 @@
 import java.util.Set;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   public void m1() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   public void m2() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   public void m3() {}
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/pckg/PackageMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/pckg/PackageMethodsTest.java
index ef4e527..e2f3886 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/pckg/PackageMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/pckg/PackageMethodsTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.methods.MethodsTestBase;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -17,17 +17,17 @@
 import java.util.Set;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   void m1() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   void m2() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   void m3() {}
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/prvt/PrivateMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/prvt/PrivateMethodsTest.java
index 98be19a..bb5b668 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/prvt/PrivateMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/prvt/PrivateMethodsTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.methods.MethodsTestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -18,17 +18,17 @@
 import java.util.Set;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   private void m1() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   private void m2() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   private void m3() {}
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/shadow/ShadowMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/shadow/ShadowMethodsTest.java
index e10cca9..5f8bc2e 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/shadow/ShadowMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/shadow/ShadowMethodsTest.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.shaking.methods.MethodsTestBase;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -17,19 +17,19 @@
 import java.util.Collection;
 import org.junit.Test;
 
-@NeverMerge
+@NoVerticalClassMerging
 class Super {
   public void m1() {}
   private void m2() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class Sub extends Super {
   public void m1() {}
   private void m2() {}
 }
 
-@NeverMerge
+@NoVerticalClassMerging
 class SubSub extends Sub {
   public void m1() {}
   public void m2() {}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
index cf97eeb..4536cd9 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/A.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.shaking.testrules;
 
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoStaticClassMerging;
 
-@NeverMerge
+@NoStaticClassMerging
 public class A {
 
   public static int m(int a, int b) {
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 382ee13..41533ec 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
@@ -42,6 +42,7 @@
     return testForR8(parameters.getBackend())
         .addProgramClasses(Main.class, A.class, B.class, C.class)
         .addKeepRules(proguardConfiguration)
+        .enableNoStaticClassMergingAnnotations()
         .enableProguardTestOptions()
         .compile()
         .inspector();
@@ -53,7 +54,6 @@
         runTest(
             ImmutableList.of(
                 "-keep class **.Main { *; }",
-                "-nevermerge @com.android.tools.r8.NeverMerge class *",
                 "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
                 "-dontobfuscate"));
 
@@ -84,7 +84,6 @@
                 "-neverinline class **.A { method(); }",
                 "-neverinline class **.B { method(); }",
                 "-keep class **.Main { *; }",
-                "-nevermerge @com.android.tools.r8.NeverMerge class *",
                 "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
                 "-dontobfuscate"));
 
@@ -112,7 +111,6 @@
                 "-forceinline class **.A { int m(int, int); }",
                 "-forceinline class **.B { int m(int, int); }",
                 "-keep class **.Main { *; }",
-                "-nevermerge @com.android.tools.r8.NeverMerge class *",
                 "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
                 "-dontobfuscate"));
 
@@ -130,7 +128,6 @@
           ImmutableList.of(
               "-forceinline class **.A { int x(); }",
               "-keep class **.Main { *; }",
-              "-nevermerge @com.android.tools.r8.NeverMerge class *",
               "-dontobfuscate"));
       fail("Force inline of non-inlinable method succeeded");
     } catch (Throwable t) {
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
index 39d3aaf..79c5edc 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.Reference;
@@ -53,7 +53,7 @@
             "-whyareyoukeeping class **.*$" + targetClass.getSimpleName() + " { void gone(); }")
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .minification(minification)
         .setMinApi(AndroidApiLevel.B)
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
@@ -71,7 +71,7 @@
         .addKeepMainRule(main)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .enableMergeAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .minification(minification)
         .setMinApi(AndroidApiLevel.B)
         .setKeptGraphConsumer(graphConsumer)
@@ -114,7 +114,7 @@
     testViaConsumer(TestMain2.class, Itf.class, Impl.class);
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   static class Base {
     @NeverInline
     public void gone() {
@@ -137,7 +137,7 @@
     }
   }
 
-  @NeverMerge
+  @NoVerticalClassMerging
   interface Itf {
     void gone();
   }
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 04a8671..fe4d4d9 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
@@ -12,6 +12,7 @@
 import java.util.List;
 import java.util.function.Consumer;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.rules.TemporaryFolder;
 
 public class AbsentClassSubject extends ClassSubject {
 
@@ -96,7 +97,7 @@
 
   @Override
   public String getOriginalName() {
-    return null;
+    return reference.getTypeName();
   }
 
   @Override
@@ -190,7 +191,12 @@
   }
 
   @Override
-  public void disassembleUsingJavap(boolean verbose) throws Exception {
+  public String disassembleUsingJavap(boolean verbose) throws Exception {
     throw new Unreachable("Cannot disassembly an absent class");
   }
+
+  @Override
+  public String asmify(TemporaryFolder tempFolder, boolean debug) throws Exception {
+    throw new Unreachable("Cannot asmify an absent class");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index c62f945..f81dffd 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -74,6 +74,11 @@
   }
 
   @Override
+  public boolean isSynchronized() {
+    throw new Unreachable("Cannot determine if an absent method is synchronized");
+  }
+
+  @Override
   public boolean isInstanceInitializer() {
     throw new Unreachable("Cannot determine if an absent method is an instance initializer");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 6cf80fb..4e6a3f5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -351,7 +351,6 @@
     return instruction instanceof CfArrayStore;
   }
 
-
   @Override
   public int size() {
     // TODO(b/122302789): CfInstruction#getSize()
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 d3c1ecc..96b0d0c 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
@@ -25,6 +25,7 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.rules.TemporaryFolder;
 
 public abstract class ClassSubject extends Subject {
 
@@ -217,5 +218,7 @@
 
   public abstract ClassNamingForNameMapper getNaming();
 
-  public abstract void disassembleUsingJavap(boolean verbose) throws Exception;
+  public abstract String disassembleUsingJavap(boolean verbose) throws Exception;
+
+  public abstract String asmify(TemporaryFolder tempFolder, boolean debug) throws Exception;
 }
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 8d925da..dbdff3d 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
@@ -30,10 +30,14 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.rules.TemporaryFolder;
 
 public class FoundClassSubject extends ClassSubject {
 
@@ -191,7 +195,7 @@
   public FieldSubject uniqueFieldWithName(String name) {
     FieldSubject fieldSubject = null;
     for (FoundFieldSubject candidate : allFields()) {
-      if (candidate.getOriginalName().equals(name)) {
+      if (candidate.getOriginalName(false).equals(name)) {
         assert fieldSubject == null;
         fieldSubject = candidate;
       }
@@ -424,7 +428,7 @@
   }
 
   @Override
-  public void disassembleUsingJavap(boolean verbose) throws Exception {
+  public String disassembleUsingJavap(boolean verbose) throws Exception {
     assert dexClass.origin != null;
     List<String> command = new ArrayList<>();
     command.add(
@@ -442,5 +446,34 @@
     ProcessResult processResult = ToolHelper.runProcess(new ProcessBuilder(command));
     assert processResult.exitCode == 0;
     System.out.println(processResult.stdout);
+    return processResult.stdout;
+  }
+
+  @Override
+  public String asmify(TemporaryFolder tempFolder, boolean debug) throws Exception {
+    assert dexClass.origin != null;
+    List<String> parts = dexClass.origin.parts();
+    assert parts.size() == 2;
+    String directory = parts.get(0);
+    String fileName = parts.get(1);
+    if (directory.endsWith(".jar") || directory.endsWith(".zip")) {
+      File tempOut = tempFolder.newFolder();
+      ZipUtils.unzip(directory, tempOut);
+      directory = tempOut.getAbsolutePath();
+    }
+    List<String> command = new ArrayList<>();
+    command.add(
+        CfRuntime.getCheckedInJdk9().getJavaHome().resolve("bin").resolve("java").toString());
+    command.add("-cp");
+    command.add(ToolHelper.ASM_JAR + ":" + ToolHelper.ASM_UTIL_JAR);
+    command.add("org.objectweb.asm.util.ASMifier");
+    if (!debug) {
+      command.add("-debug");
+    }
+    command.add(Paths.get(directory, fileName).toString());
+    ProcessResult processResult = ToolHelper.runProcess(new ProcessBuilder(command));
+    assert processResult.exitCode == 0;
+    System.out.println(processResult.stdout);
+    return processResult.stdout;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 299a6c0..536aa5e3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -122,6 +122,11 @@
   }
 
   @Override
+  public boolean isSynchronized() {
+    return dexMethod.accessFlags.isSynchronized();
+  }
+
+  @Override
   public boolean isInstanceInitializer() {
     return dexMethod.isInstanceInitializer();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index f2b7fe3..04f007b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import static org.hamcrest.CoreMatchers.not;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexClass;
@@ -107,7 +109,7 @@
 
       @Override
       public void describeTo(final Description description) {
-        description.appendText(" present");
+        description.appendText("present");
       }
 
       @Override
@@ -660,4 +662,11 @@
       return getClassName() + "." + getMethodName() + "(" + filename + ":" + originalPosition + ")";
     }
   }
+
+  public static <T> Matcher<T> notIf(Matcher<T> matcher, boolean condition) {
+    if (condition) {
+      return not(matcher);
+    }
+    return matcher;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 690838d..3c86361 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -25,6 +25,8 @@
 
   public abstract boolean isBridge();
 
+  public abstract boolean isSynchronized();
+
   public abstract boolean isInstanceInitializer();
 
   public abstract boolean isClassInitializer();
diff --git a/tools/test.py b/tools/test.py
index 5bc9d6c..fbc1985 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -138,6 +138,9 @@
   result.add_option('--worktree',
       default=False, action='store_true',
       help='Tests are run in worktree and should not use gradle user home.')
+  result.add_option('--horizontal-class-merging', '--horizontal_class_merging',
+      help='Enable horizontal class merging.',
+      default=False, action='store_true')
   result.add_option('--runtimes',
       default=None,
       help='Test parameter runtimes to use, separated by : (eg, none:jdk9).'