Reland "Unify main-dex-classes and main-dex-tracing into one structure"

This reverts commit 94932b142e611f5c9c9b7546df221a996ed395ab.

Change-Id: Iaf895e8fbb1bf36e6ee816bdabf8b58f03070432
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 7310ecf..7335dbb 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.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.AndroidApp;
@@ -184,9 +185,9 @@
       SyntheticItems.collectSyntheticInputs(appView);
 
       if (!options.mainDexKeepRules.isEmpty()) {
-        new GenerateMainDexList(options)
-            .traceMainDex(
-                executor, appView.appInfo().app(), appView.appInfo().getMainDexClasses()::addAll);
+        MainDexInfo mainDexInfo =
+            new GenerateMainDexList(options).traceMainDex(executor, appView.appInfo().app());
+        appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
       }
 
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -300,7 +301,7 @@
           appView.setAppInfo(
               new AppInfo(
                   appView.appInfo().getSyntheticItems().commit(app),
-                  appView.appInfo().getMainDexClasses()));
+                  appView.appInfo().getMainDexInfo()));
           namingLens = NamingLens.getIdentityLens();
         }
 
@@ -358,7 +359,7 @@
     appView.setAppInfo(
         new AppInfo(
             appView.appInfo().getSyntheticItems().commit(cfApp),
-            appView.appInfo().getMainDexClasses()));
+            appView.appInfo().getMainDexInfo()));
     ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
     NamingLens prefixRewritingNamingLens =
         PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 0230729..7896d57 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 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.shaking.MainDexInfo;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FeatureClassMapping;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
@@ -75,7 +75,7 @@
       ApplicationReader applicationReader =
           new ApplicationReader(command.getInputApp(), options, timing);
       DexApplication app = applicationReader.read(executor);
-      MainDexClasses mainDexClasses = applicationReader.readMainDexClasses(app);
+      MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(app);
 
       List<Marker> markers = app.dexItemFactory.extractMarkers();
 
@@ -94,7 +94,7 @@
         // If this is the base, we add the main dex list.
         AppInfo appInfo =
             feature.equals(featureClassMapping.getBaseName())
-                ? AppInfo.createInitialAppInfo(featureApp, mainDexClasses)
+                ? AppInfo.createInitialAppInfo(featureApp, mainDexInfo)
                 : AppInfo.createInitialAppInfo(featureApp);
         AppView<AppInfo> appView = AppView.createForD8(appInfo);
 
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 1e3d725..b3e100a 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -16,8 +16,8 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.MainDexListBuilder;
-import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.shaking.RootSetUtils.MainDexRootSet;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.utils.AndroidApp;
@@ -28,20 +28,16 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
 
 @Keep
 public class GenerateMainDexList {
   private final Timing timing = new Timing("maindex");
   private final InternalOptions options;
 
-  private List<String> result = null;
-
   public GenerateMainDexList(InternalOptions options) {
     this.options = options;
   }
@@ -49,32 +45,24 @@
   private List<String> run(AndroidApp app, ExecutorService executor)
       throws IOException {
     try {
+      // TODO(b/178231294): Clean up this such that we do not both return the result and call the
+      //  consumer.
       DexApplication application = new ApplicationReader(app, options, timing).read(executor);
-      traceMainDex(
-          executor,
-          application,
-          mainDexTracingResult -> {
-            result =
-                mainDexTracingResult.getClasses().stream()
-                    .map(c -> c.toSourceString().replace('.', '/') + ".class")
-                    .sorted()
-                    .collect(Collectors.toList());
-
-            if (options.mainDexListConsumer != null) {
-              options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
-              options.mainDexListConsumer.finished(options.reporter);
-            }
-          });
+      List<String> result = new ArrayList<>();
+      traceMainDex(executor, application)
+          .forEach(type -> result.add(type.toBinaryName() + ".class"));
+      Collections.sort(result);
+      if (options.mainDexListConsumer != null) {
+        options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
+        options.mainDexListConsumer.finished(options.reporter);
+      }
       return result;
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
     }
   }
 
-  public void traceMainDex(
-      ExecutorService executor,
-      DexApplication application,
-      Consumer<MainDexTracingResult> resultConsumer)
+  public MainDexInfo traceMainDex(ExecutorService executor, DexApplication application)
       throws ExecutionException {
     AppView<? extends AppInfoWithClassHierarchy> appView =
         AppView.createForR8(application.toDirect());
@@ -97,25 +85,19 @@
 
     Enqueuer enqueuer =
         EnqueuerFactory.createForFinalMainDexTracing(
-            appView, executor, subtypingInfo, graphConsumer, MainDexTracingResult.NONE);
-    Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(executor, timing);
-    // LiveTypes is the result.
-    MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
-    resultConsumer.accept(mainDexTracingResult);
-
+            appView, executor, subtypingInfo, graphConsumer);
+    MainDexInfo mainDexInfo = enqueuer.traceMainDex(executor, timing);
     R8.processWhyAreYouKeepingAndCheckDiscarded(
         mainDexRootSet,
         () -> {
           ArrayList<DexProgramClass> classes = new ArrayList<>();
           // TODO(b/131668850): This is not a deterministic order!
-          mainDexTracingResult
-              .getClasses()
-              .forEach(
-                  type -> {
-                    DexClass clazz = appView.definitionFor(type);
-                    assert clazz.isProgramClass();
-                    classes.add(clazz.asProgramClass());
-                  });
+          mainDexInfo.forEach(
+              type -> {
+                DexClass clazz = appView.definitionFor(type);
+                assert clazz.isProgramClass();
+                classes.add(clazz.asProgramClass());
+              });
           return classes;
         },
         whyAreYouKeepingConsumer,
@@ -125,6 +107,8 @@
         options,
         timing,
         executor);
+
+    return mainDexInfo;
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 782094c..25f4d3b 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -27,7 +27,7 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -364,7 +364,7 @@
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
             application,
             ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-            MainDexClasses.createEmptyMainDexClasses());
+            MainDexInfo.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 a79b303..eef9f3d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -88,9 +88,8 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.Mode;
 import com.android.tools.r8.shaking.EnqueuerFactory;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.MainDexListBuilder;
-import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationUtils;
@@ -285,12 +284,12 @@
       {
         ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
         DirectMappedDexApplication application = applicationReader.read(executorService).toDirect();
-        MainDexClasses mainDexClasses = applicationReader.readMainDexClasses(application);
+        MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(application);
 
         // Now that the dex-application is fully loaded, close any internal archive providers.
         inputApp.closeInternalArchiveProviders();
 
-        appView = AppView.createForR8(application, mainDexClasses);
+        appView = AppView.createForR8(application, mainDexInfo);
         appView.setAppServices(AppServices.builder(appView).build());
       }
 
@@ -423,8 +422,7 @@
       // Build conservative main dex content after first round of tree shaking. This is used
       // by certain optimizations to avoid introducing additional class references into main dex
       // classes, as that can cause the final number of main dex methods to grow.
-      MainDexTracingResult mainDexTracingResult =
-          performInitialMainDexTracing(appView, executorService);
+      performInitialMainDexTracing(appView, executorService);
 
       // The class type lattice elements include information about the interfaces that a class
       // implements. This information can change as a result of vertical class merging, so we need
@@ -466,11 +464,7 @@
           timing.begin("VerticalClassMerger");
           VerticalClassMerger verticalClassMerger =
               new VerticalClassMerger(
-                  getDirectApp(appViewWithLiveness),
-                  appViewWithLiveness,
-                  executorService,
-                  timing,
-                  mainDexTracingResult);
+                  getDirectApp(appViewWithLiveness), appViewWithLiveness, executorService, timing);
           VerticalClassMergerGraphLens lens = verticalClassMerger.run();
           if (lens != null) {
             appView.rewriteWithLens(lens);
@@ -517,7 +511,7 @@
           DirectMappedDexApplication.Builder appBuilder =
               appView.appInfo().app().asDirect().builder();
           HorizontalClassMergerResult horizontalClassMergerResult =
-              merger.run(appBuilder, mainDexTracingResult, runtimeTypeCheckInfo);
+              merger.run(appBuilder, runtimeTypeCheckInfo);
           if (horizontalClassMergerResult != null) {
             // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that
             // allocations sites, fields accesses, etc. are correctly transferred to the target
@@ -534,8 +528,6 @@
                     .addRemovedClasses(appView.horizontallyMergedClasses().getSources())
                     .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getTargets())
                     .build());
-
-            mainDexTracingResult = horizontalClassMergerResult.getMainDexTracingResult();
           }
           timing.end();
         } else {
@@ -558,7 +550,7 @@
       timing.begin("Create IR");
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
-        IRConverter converter = new IRConverter(appView, timing, printer, mainDexTracingResult);
+        IRConverter converter = new IRConverter(appView, timing, printer);
         DexApplication application =
             converter.optimize(appViewWithLiveness, executorService).asDirect();
         appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(previous -> application));
@@ -669,12 +661,6 @@
                 .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
                 .build(appView.withLiveness(), removedClasses)
                 .run();
-            if (!mainDexTracingResult.isEmpty()) {
-              // Remove types that no longer exists from the computed main dex list.
-              mainDexTracingResult =
-                  mainDexTracingResult.prunedCopy(appView.appInfo().withLiveness());
-            }
-
             // Synthesize fields for triggering class initializers.
             new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService);
           }
@@ -687,7 +673,7 @@
             appView.protoShrinker().enumLiteProtoShrinker.verifyDeadEnumLiteMapsAreDead();
           }
 
-          IRConverter converter = new IRConverter(appView, timing, null, mainDexTracingResult);
+          IRConverter converter = new IRConverter(appView, timing, null);
 
           // If proto shrinking is enabled, we need to reprocess every dynamicMethod(). This ensures
           // that proto fields that have been removed by the second round of tree shaking are also
@@ -705,8 +691,7 @@
         }
       }
 
-      mainDexTracingResult =
-          performFinalMainDexTracing(appView, executorService, mainDexTracingResult);
+      performFinalMainDexTracing(appView, executorService);
 
       // Remove unneeded visibility bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
@@ -744,11 +729,6 @@
         assert Repackaging.verifyIdentityRepackaging(appView.withLiveness());
       }
 
-      // Add automatic main dex classes to an eventual manual list of classes.
-      if (!options.mainDexKeepRules.isEmpty()) {
-        appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
-      }
-
       if (appView.appInfo().hasLiveness()) {
         SyntheticFinalization.finalizeWithLiveness(appView.withLiveness());
       } else {
@@ -854,11 +834,11 @@
     }
   }
 
-  private MainDexTracingResult performInitialMainDexTracing(
+  private void performInitialMainDexTracing(
       AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService)
       throws ExecutionException {
     if (options.mainDexKeepRules.isEmpty()) {
-      return MainDexTracingResult.NONE;
+      return;
     }
     assert appView.graphLens().isIdentityLens();
     // Find classes which may have code executed before secondary dex files installation.
@@ -867,22 +847,19 @@
         MainDexRootSet.builder(appView, subtypingInfo, options.mainDexKeepRules)
             .build(executorService);
     appView.setMainDexRootSet(mainDexRootSet);
+    appView.appInfo().unsetObsolete();
     // Live types is the tracing result.
-    Set<DexProgramClass> mainDexBaseClasses =
+    MainDexInfo mainDexInfo =
         EnqueuerFactory.createForInitialMainDexTracing(appView, executorService, subtypingInfo)
             .traceMainDex(executorService, timing);
-    appView.appInfo().unsetObsolete();
-    // Calculate the automatic main dex list according to legacy multidex constraints.
-    return new MainDexListBuilder(mainDexBaseClasses, appView).run();
+    appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
   }
 
-  private MainDexTracingResult performFinalMainDexTracing(
-      AppView<AppInfoWithClassHierarchy> appView,
-      ExecutorService executorService,
-      MainDexTracingResult previousTracingResult)
+  private void performFinalMainDexTracing(
+      AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService)
       throws ExecutionException {
     if (options.mainDexKeepRules.isEmpty()) {
-      return MainDexTracingResult.NONE;
+      return;
     }
     // No need to build a new main dex root set
     assert appView.getMainDexRootSet() != null;
@@ -895,31 +872,22 @@
 
     Enqueuer enqueuer =
         EnqueuerFactory.createForFinalMainDexTracing(
-            appView,
-            executorService,
-            new SubtypingInfo(appView),
-            mainDexKeptGraphConsumer,
-            previousTracingResult);
+            appView, executorService, new SubtypingInfo(appView), mainDexKeptGraphConsumer);
     // Find classes which may have code executed before secondary dex files installation.
-    // Live types is the tracing result.
-    Set<DexProgramClass> mainDexBaseClasses = enqueuer.traceMainDex(executorService, timing);
-    // Calculate the automatic main dex list according to legacy multidex constraints.
-    MainDexTracingResult mainDexTracingResult =
-        new MainDexListBuilder(mainDexBaseClasses, appView).run();
+    MainDexInfo mainDexInfo = enqueuer.traceMainDex(executorService, timing);
+    appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
 
     processWhyAreYouKeepingAndCheckDiscarded(
         appView.getMainDexRootSet(),
         () -> {
           ArrayList<DexProgramClass> classes = new ArrayList<>();
           // TODO(b/131668850): This is not a deterministic order!
-          mainDexTracingResult
-              .getClasses()
-              .forEach(
-                  type -> {
-                    DexClass clazz = appView.definitionFor(type);
-                    assert clazz.isProgramClass();
-                    classes.add(clazz.asProgramClass());
-                  });
+          mainDexInfo.forEach(
+              type -> {
+                DexClass clazz = appView.definitionFor(type);
+                assert clazz.isProgramClass();
+                classes.add(clazz.asProgramClass());
+              });
           return classes;
         },
         whyAreYouKeepingConsumer,
@@ -929,8 +897,6 @@
         options,
         timing,
         executorService);
-
-    return mainDexTracingResult;
   }
 
   private static boolean verifyMovedMethodsHaveOriginalMethodPosition(
@@ -1062,11 +1028,7 @@
       if (forMainDex) {
         enqueuer =
             EnqueuerFactory.createForFinalMainDexTracing(
-                appView,
-                executorService,
-                subtypingInfo,
-                whyAreYouKeepingConsumer,
-                MainDexTracingResult.NONE);
+                appView, executorService, subtypingInfo, whyAreYouKeepingConsumer);
         enqueuer.traceMainDex(executorService, timing);
       } else {
         enqueuer =
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 4b035d4..23db0d9 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -29,7 +29,7 @@
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ClassProvider;
@@ -198,8 +198,8 @@
     }
   }
 
-  public MainDexClasses readMainDexClasses(DexApplication app) {
-    MainDexClasses.Builder builder = MainDexClasses.builder();
+  public MainDexInfo readMainDexClasses(DexApplication app) {
+    MainDexInfo.Builder builder = MainDexInfo.builder();
     if (inputApp.hasMainDexList()) {
       for (StringResource resource : inputApp.getMainDexListResources()) {
         addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, itemFactory));
@@ -211,15 +211,15 @@
               .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
               .collect(Collectors.toList()));
     }
-    return builder.build();
+    return builder.buildList();
   }
 
   private void addToMainDexClasses(
-      DexApplication app, MainDexClasses.Builder builder, Iterable<DexType> types) {
+      DexApplication app, MainDexInfo.Builder builder, Iterable<DexType> types) {
     for (DexType type : types) {
       DexProgramClass clazz = app.programDefinitionFor(type);
       if (clazz != null) {
-        builder.add(clazz);
+        builder.addList(clazz);
       } else if (!options.ignoreMainDexMissingClasses) {
         options.reporter.warning(
             new StringDiagnostic(
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index ab72b04..da3b04c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -45,7 +45,7 @@
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -199,7 +199,7 @@
           options.getDexFilePerClassFileConsumer().combineSyntheticClassesWithPrimaryClass());
     } else if (!options.canUseMultidex()
         && options.mainDexKeepRules.isEmpty()
-        && appView.appInfo().getMainDexClasses().isEmpty()
+        && appView.appInfo().getMainDexInfo().isEmpty()
         && options.enableMainDexListCheck) {
       distributor = new VirtualFile.MonoDexDistributor(this, options);
     } else {
@@ -688,10 +688,11 @@
   }
 
   private static String writeMainDexList(AppView<?> appView, NamingLens namingLens) {
-    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
+    // TODO(b/178231294): Clean up by streaming directly to the consumer.
+    MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
     StringBuilder builder = new StringBuilder();
-    List<DexType> list = new ArrayList<>(mainDexClasses.size());
-    mainDexClasses.forEach(list::add);
+    List<DexType> list = new ArrayList<>(mainDexInfo.size());
+    mainDexInfo.forEach(list::add);
     list.sort(DexType::compareTo);
     list.forEach(
         type -> builder.append(mapMainDexListName(type, namingLens)).append('\n'));
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 414031e..8f56827 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -27,7 +27,7 @@
 import com.android.tools.r8.logging.Log;
 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.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -394,13 +394,14 @@
     }
 
     protected void fillForMainDexList(Set<DexProgramClass> classes) {
-      MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
-      if (mainDexClasses.isEmpty()) {
+      MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
+      if (mainDexInfo.isEmpty()) {
         return;
       }
       VirtualFile mainDexFile = virtualFiles.get(0);
-      mainDexClasses.forEach(
+      mainDexInfo.forEach(
           type -> {
+            // TODO(b/178577273): We should ensure only live types in main dex.
             DexProgramClass clazz =
                 asProgramClassOrNull(appView.appInfo().definitionForWithoutExistenceAssert(type));
             if (clazz != null) {
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 302dff4..d8785d1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -7,7 +7,7 @@
 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.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanBox;
@@ -20,7 +20,7 @@
 
   private final DexApplication app;
   private final DexItemFactory dexItemFactory;
-  private final MainDexClasses mainDexClasses;
+  private final MainDexInfo mainDexInfo;
   private final SyntheticItems syntheticItems;
 
   // Set when a new AppInfo replaces a previous one. All public methods should verify that the
@@ -28,37 +28,36 @@
   private final BooleanBox obsolete;
 
   public static AppInfo createInitialAppInfo(DexApplication application) {
-    return createInitialAppInfo(application, MainDexClasses.createEmptyMainDexClasses());
+    return createInitialAppInfo(application, MainDexInfo.createEmptyMainDexClasses());
   }
 
-  public static AppInfo createInitialAppInfo(
-      DexApplication application, MainDexClasses mainDexClasses) {
-    return new AppInfo(SyntheticItems.createInitialSyntheticItems(application), mainDexClasses);
+  public static AppInfo createInitialAppInfo(DexApplication application, MainDexInfo mainDexInfo) {
+    return new AppInfo(SyntheticItems.createInitialSyntheticItems(application), mainDexInfo);
   }
 
-  public AppInfo(CommittedItems committedItems, MainDexClasses mainDexClasses) {
+  public AppInfo(CommittedItems committedItems, MainDexInfo mainDexInfo) {
     this(
         committedItems.getApplication(),
         committedItems.toSyntheticItems(),
-        mainDexClasses,
+        mainDexInfo,
         new BooleanBox());
   }
 
   // For desugaring.
   // This is a view onto the app info and is the only place the pending synthetics are shared.
   AppInfo(AppInfoWithClassHierarchy.CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
-    this(appInfo.app, appInfo.syntheticItems, appInfo.mainDexClasses, appInfo.obsolete);
+    this(appInfo.app, appInfo.syntheticItems, appInfo.mainDexInfo, appInfo.obsolete);
     assert witness != null;
   }
 
   private AppInfo(
       DexApplication application,
       SyntheticItems syntheticItems,
-      MainDexClasses mainDexClasses,
+      MainDexInfo mainDexInfo,
       BooleanBox obsolete) {
     this.app = application;
     this.dexItemFactory = application.dexItemFactory;
-    this.mainDexClasses = mainDexClasses;
+    this.mainDexInfo = mainDexInfo;
     this.syntheticItems = syntheticItems;
     this.obsolete = obsolete;
   }
@@ -72,7 +71,12 @@
     }
     return new AppInfo(
         getSyntheticItems().commitPrunedItems(prunedItems),
-        getMainDexClasses().withoutPrunedItems(prunedItems));
+        getMainDexInfo().withoutPrunedItems(prunedItems));
+  }
+
+  public AppInfo rebuildWithMainDexInfo(MainDexInfo mainDexInfo) {
+    assert checkIfObsolete();
+    return new AppInfo(app, syntheticItems, mainDexInfo, new BooleanBox());
   }
 
   protected InternalOptions options() {
@@ -107,19 +111,29 @@
     return dexItemFactory;
   }
 
-  public MainDexClasses getMainDexClasses() {
-    return mainDexClasses;
+  public MainDexInfo getMainDexInfo() {
+    assert checkIfObsolete();
+    return mainDexInfo;
   }
 
   public SyntheticItems getSyntheticItems() {
+    assert checkIfObsolete();
     return syntheticItems;
   }
 
-  public void addSynthesizedClass(DexProgramClass clazz, boolean addToMainDexClasses) {
+  public void addSynthesizedClass(DexProgramClass clazz, boolean addToMainDex) {
     assert checkIfObsolete();
     syntheticItems.addLegacySyntheticClass(clazz);
-    if (addToMainDexClasses && !mainDexClasses.isEmpty()) {
-      mainDexClasses.add(clazz);
+    if (addToMainDex) {
+      mainDexInfo.addSyntheticClass(clazz);
+    }
+  }
+
+  public void addSynthesizedClass(DexProgramClass clazz, ProgramDefinition context) {
+    assert checkIfObsolete();
+    syntheticItems.addLegacySyntheticClass(clazz);
+    if (context != null) {
+      mainDexInfo.addLegacySyntheticClass(clazz, context);
     }
   }
 
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 8ff4d1e..cd90e16 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
@@ -56,11 +56,11 @@
   public static AppInfoWithClassHierarchy createInitialAppInfoWithClassHierarchy(
       DexApplication application,
       ClassToFeatureSplitMap classToFeatureSplitMap,
-      MainDexClasses mainDexClasses) {
+      MainDexInfo mainDexInfo) {
     return new AppInfoWithClassHierarchy(
         SyntheticItems.createInitialSyntheticItems(application),
         classToFeatureSplitMap,
-        mainDexClasses,
+        mainDexInfo,
         MissingClasses.empty());
   }
 
@@ -74,9 +74,9 @@
   protected AppInfoWithClassHierarchy(
       CommittedItems committedItems,
       ClassToFeatureSplitMap classToFeatureSplitMap,
-      MainDexClasses mainDexClasses,
+      MainDexInfo mainDexInfo,
       MissingClasses missingClasses) {
-    super(committedItems, mainDexClasses);
+    super(committedItems, mainDexInfo);
     this.classToFeatureSplitMap = classToFeatureSplitMap;
     this.missingClasses = missingClasses;
   }
@@ -97,27 +97,36 @@
 
   public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(CommittedItems commit) {
     return new AppInfoWithClassHierarchy(
-        commit, getClassToFeatureSplitMap(), getMainDexClasses(), getMissingClasses());
+        commit, getClassToFeatureSplitMap(), getMainDexInfo(), getMissingClasses());
   }
 
   public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(MissingClasses missingClasses) {
     return new AppInfoWithClassHierarchy(
         getSyntheticItems().commit(app()),
         getClassToFeatureSplitMap(),
-        getMainDexClasses(),
+        getMainDexInfo(),
         missingClasses);
   }
 
   public AppInfoWithClassHierarchy rebuildWithClassHierarchy(
       Function<DexApplication, DexApplication> fn) {
+    assert checkIfObsolete();
     return new AppInfoWithClassHierarchy(
         getSyntheticItems().commit(fn.apply(app())),
         getClassToFeatureSplitMap(),
-        getMainDexClasses(),
+        getMainDexInfo(),
         getMissingClasses());
   }
 
   @Override
+  public AppInfoWithClassHierarchy rebuildWithMainDexInfo(MainDexInfo mainDexInfo) {
+    assert getClass() == AppInfoWithClassHierarchy.class;
+    assert checkIfObsolete();
+    return new AppInfoWithClassHierarchy(
+        getSyntheticItems().commit(app()), classToFeatureSplitMap, mainDexInfo, missingClasses);
+  }
+
+  @Override
   public AppInfoWithClassHierarchy prunedCopyFrom(PrunedItems prunedItems) {
     assert getClass() == AppInfoWithClassHierarchy.class;
     assert checkIfObsolete();
@@ -128,7 +137,7 @@
     return new AppInfoWithClassHierarchy(
         getSyntheticItems().commitPrunedItems(prunedItems),
         getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
-        getMainDexClasses().withoutPrunedItems(prunedItems),
+        getMainDexInfo().withoutPrunedItems(prunedItems),
         getMissingClasses());
   }
 
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 96df40d..954580b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -31,7 +31,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.ProguardCompatibilityActions;
 import com.android.tools.r8.shaking.RootSetUtils.MainDexRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
@@ -97,7 +97,6 @@
   private EnumDataMap unboxedEnums = EnumDataMap.empty();
   // TODO(b/169115389): Remove
   private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
-
   private Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
 
   // When input has been (partially) desugared these are the classes which has been library
@@ -157,16 +156,16 @@
   }
 
   public static AppView<AppInfoWithClassHierarchy> createForR8(DexApplication application) {
-    return createForR8(application, MainDexClasses.createEmptyMainDexClasses());
+    return createForR8(application, MainDexInfo.createEmptyMainDexClasses());
   }
 
   public static AppView<AppInfoWithClassHierarchy> createForR8(
-      DexApplication application, MainDexClasses mainDexClasses) {
+      DexApplication application, MainDexInfo mainDexInfo) {
     ClassToFeatureSplitMap classToFeatureSplitMap =
         ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(application.options);
     AppInfoWithClassHierarchy appInfo =
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
-            application, classToFeatureSplitMap, mainDexClasses);
+            application, classToFeatureSplitMap, mainDexInfo);
     return new AppView<>(
         appInfo, WholeProgramOptimizations.ON, defaultPrefixRewritingMapper(appInfo));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 50fcfbf..56d3f52 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -300,6 +300,10 @@
    */
   private GraphLens() {}
 
+  public boolean isSyntheticFinalizationGraphLens() {
+    return false;
+  }
+
   public abstract DexType getOriginalType(DexType type);
 
   public abstract Iterable<DexType> getOriginalTypes(DexType type);
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 47a1625..d97f12f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -29,7 +29,8 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex;
+import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoDifferentMainDexGroups;
+import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDexList;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
 import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
 import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
@@ -39,7 +40,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
-import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -60,12 +60,11 @@
   // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
   public HorizontalClassMergerResult run(
       DirectMappedDexApplication.Builder appBuilder,
-      MainDexTracingResult mainDexTracingResult,
       RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder());
 
     // Run the policies on all program classes to produce a final grouping.
-    List<Policy> policies = getPolicies(mainDexTracingResult, runtimeTypeCheckInfo);
+    List<Policy> policies = getPolicies(runtimeTypeCheckInfo);
     Collection<MergeGroup> groups =
         new SimplePolicyExecutor().run(Collections.singletonList(initialGroup), policies);
 
@@ -79,8 +78,6 @@
         new HorizontallyMergedClasses.Builder();
     HorizontalClassMergerGraphLens.Builder lensBuilder =
         new HorizontalClassMergerGraphLens.Builder();
-    MainDexTracingResult.Builder mainDexTracingResultBuilder =
-        mainDexTracingResult.extensionBuilder(appView.appInfo());
 
     // Set up a class merger for each group.
     List<ClassMerger> classMergers =
@@ -91,9 +88,7 @@
 
     // Merge the classes.
     SyntheticArgumentClass syntheticArgumentClass =
-        new SyntheticArgumentClass.Builder(
-                appBuilder, appView, mainDexTracingResult, mainDexTracingResultBuilder)
-            .build(allMergeClasses);
+        new SyntheticArgumentClass.Builder(appBuilder, appView).build(allMergeClasses);
     applyClassMergers(classMergers, syntheticArgumentClass);
 
     // Generate the graph lens.
@@ -106,8 +101,7 @@
     KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
     keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
 
-    return new HorizontalClassMergerResult(
-        createFieldAccessInfoCollectionModifier(groups), lens, mainDexTracingResultBuilder.build());
+    return new HorizontalClassMergerResult(createFieldAccessInfoCollectionModifier(groups), lens);
   }
 
   private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
@@ -126,8 +120,7 @@
     return builder.build();
   }
 
-  private List<Policy> getPolicies(
-      MainDexTracingResult mainDexTracingResult, RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+  private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     return ImmutableList.of(
         new NotMatchedByNoHorizontalClassMerging(appView),
         new SameInstanceFields(appView),
@@ -147,8 +140,9 @@
         new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
         new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
         new PreventMethodImplementation(appView),
-        new DontInlinePolicy(appView, mainDexTracingResult),
-        new PreventMergeIntoMainDex(appView, mainDexTracingResult),
+        new DontInlinePolicy(appView),
+        new PreventMergeIntoMainDexList(appView),
+        new PreventMergeIntoDifferentMainDexGroups(appView),
         new AllInstantiatedOrUninstantiated(appView),
         new SameParentClass(),
         new SameNestHost(),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
index 75aafb4..820c24a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
@@ -5,21 +5,17 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.shaking.MainDexTracingResult;
 
 public class HorizontalClassMergerResult {
 
   private final FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier;
   private final HorizontalClassMergerGraphLens graphLens;
-  private final MainDexTracingResult mainDexTracingResult;
 
   HorizontalClassMergerResult(
       FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier,
-      HorizontalClassMergerGraphLens graphLens,
-      MainDexTracingResult mainDexTracingResult) {
+      HorizontalClassMergerGraphLens graphLens) {
     this.fieldAccessInfoCollectionModifier = fieldAccessInfoCollectionModifier;
     this.graphLens = graphLens;
-    this.mainDexTracingResult = mainDexTracingResult;
   }
 
   public FieldAccessInfoCollectionModifier getFieldAccessInfoCollectionModifier() {
@@ -29,8 +25,4 @@
   public HorizontalClassMergerGraphLens getGraphLens() {
     return graphLens;
   }
-
-  public MainDexTracingResult getMainDexTracingResult() {
-    return mainDexTracingResult;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
index ec64e8a..60c350a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexTracingResult;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -57,24 +57,15 @@
 
     private final DirectMappedDexApplication.Builder appBuilder;
     private final AppView<AppInfoWithLiveness> appView;
-    private final MainDexTracingResult mainDexTracingResult;
-    private final MainDexTracingResult.Builder mainDexTracingResultBuilder;
 
-    Builder(
-        DirectMappedDexApplication.Builder appBuilder,
-        AppView<AppInfoWithLiveness> appView,
-        MainDexTracingResult mainDexTracingResult,
-        MainDexTracingResult.Builder mainDexTracingResultBuilder) {
+    Builder(DirectMappedDexApplication.Builder appBuilder, AppView<AppInfoWithLiveness> appView) {
       this.appBuilder = appBuilder;
       this.appView = appView;
-      this.mainDexTracingResult = mainDexTracingResult;
-      this.mainDexTracingResultBuilder = mainDexTracingResultBuilder;
     }
 
     private DexType synthesizeClass(
         DexProgramClass context,
         boolean requiresMainDex,
-        boolean addToMainDexTracingResult,
         int index) {
       DexType syntheticClassType =
           appView
@@ -108,10 +99,6 @@
 
       appBuilder.addSynthesizedClass(clazz);
       appView.appInfo().addSynthesizedClass(clazz, requiresMainDex);
-      if (addToMainDexTracingResult) {
-        mainDexTracingResultBuilder.addRoot(clazz);
-      }
-
       return clazz.type;
     }
 
@@ -119,20 +106,17 @@
       // Find a fresh name in an existing package.
       DexProgramClass context = mergeClasses.iterator().next();
 
-      // Add to the main dex list if one of the merged classes is in the main dex.
-      boolean requiresMainDex = appView.appInfo().getMainDexClasses().containsAnyOf(mergeClasses);
-
-      // Also add as a root to the main dex tracing result if any of the merged classes is a root.
+      // Add as a root to the main dex tracing result if any of the merged classes is a root.
       // This is needed to satisfy an assertion in the inliner that verifies that we do not inline
       // methods with references to non-roots into classes that are roots.
-      boolean addToMainDexTracingResult = Iterables.any(mergeClasses, mainDexTracingResult::isRoot);
+      MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
+      boolean requiresMainDex = Iterables.any(mergeClasses, mainDexInfo::isMainDex);
 
       List<DexType> syntheticArgumentTypes = new ArrayList<>();
       for (int i = 0;
           i < appView.options().horizontalClassMergerOptions().getSyntheticArgumentCount();
           i++) {
-        syntheticArgumentTypes.add(
-            synthesizeClass(context, requiresMainDex, addToMainDexTracingResult, i));
+        syntheticArgumentTypes.add(synthesizeClass(context, requiresMainDex, i));
       }
 
       return new SyntheticArgumentClass(syntheticArgumentTypes);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
index 9612f98..f7d121a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
@@ -12,18 +12,16 @@
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexDirectReferenceTracer;
-import com.android.tools.r8.shaking.MainDexTracingResult;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.google.common.collect.Iterables;
 
 public class DontInlinePolicy extends SingleClassPolicy {
   private final AppView<AppInfoWithLiveness> appView;
-  private final MainDexTracingResult mainDexTracingResult;
+  private final MainDexInfo mainDexInfo;
 
-  public DontInlinePolicy(
-      AppView<AppInfoWithLiveness> appView, MainDexTracingResult mainDexTracingResult) {
+  public DontInlinePolicy(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.mainDexTracingResult = mainDexTracingResult;
+    this.mainDexInfo = appView.appInfo().getMainDexInfo();
   }
 
   private boolean disallowInlining(ProgramMethod method) {
@@ -46,14 +44,6 @@
       return true;
     }
 
-    // Constructors can have references beyond the root main dex classes. This can increase the
-    // size of the main dex dependent classes and we should bail out.
-    if (mainDexTracingResult.getRoots().contains(method.getHolderType())
-        && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
-            appView.appInfo(), method, mainDexTracingResult.getRoots())) {
-      return true;
-    }
-
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
new file mode 100644
index 0000000..52a1d98
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
@@ -0,0 +1,28 @@
+// 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.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.shaking.MainDexInfo.MainDexGroup;
+
+public class PreventMergeIntoDifferentMainDexGroups
+    extends MultiClassSameReferencePolicy<MainDexGroup> {
+
+  private final MainDexInfo mainDexInfo;
+
+  public PreventMergeIntoDifferentMainDexGroups(AppView<AppInfoWithLiveness> appView) {
+    this.mainDexInfo = appView.appInfo().getMainDexInfo();
+  }
+
+  @Override
+  public MainDexGroup getMergeKey(DexProgramClass clazz) {
+    assert !mainDexInfo.isFromList(clazz);
+    return mainDexInfo.getMergeKey(clazz);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java
deleted file mode 100644
index 783ede5..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java
+++ /dev/null
@@ -1,45 +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.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex.MainDexClassification;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.shaking.MainDexTracingResult;
-
-public class PreventMergeIntoMainDex extends MultiClassSameReferencePolicy<MainDexClassification> {
-  private final MainDexClasses mainDexClasses;
-  private final MainDexTracingResult mainDexTracingResult;
-
-  enum MainDexClassification {
-    MAIN_DEX_LIST,
-    MAIN_DEX_ROOT,
-    MAIN_DEX_DEPENDENCY,
-    NOT_IN_MAIN_DEX
-  }
-
-  public PreventMergeIntoMainDex(
-      AppView<AppInfoWithLiveness> appView, MainDexTracingResult mainDexTracingResult) {
-    this.mainDexClasses = appView.appInfo().getMainDexClasses();
-    this.mainDexTracingResult = mainDexTracingResult;
-  }
-
-  @Override
-  public MainDexClassification getMergeKey(DexProgramClass clazz) {
-    if (mainDexClasses.contains(clazz)) {
-      return MainDexClassification.MAIN_DEX_LIST;
-    }
-    if (mainDexTracingResult.isRoot(clazz)) {
-      return MainDexClassification.MAIN_DEX_ROOT;
-    }
-    if (mainDexTracingResult.isDependency(clazz)) {
-      return MainDexClassification.MAIN_DEX_DEPENDENCY;
-    }
-    return MainDexClassification.NOT_IN_MAIN_DEX;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java
new file mode 100644
index 0000000..850d02e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.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.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexInfo;
+
+public class PreventMergeIntoMainDexList extends SingleClassPolicy {
+
+  private final MainDexInfo mainDexInfo;
+
+  public PreventMergeIntoMainDexList(AppView<AppInfoWithLiveness> appView) {
+    this.mainDexInfo = appView.appInfo().getMainDexInfo();
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return mainDexInfo.canMerge(program);
+  }
+}
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 22ee5c5..b1e68ace 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
@@ -91,7 +91,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.MainDexTracingResult;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -121,7 +120,6 @@
   private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
 
   public final AppView<?> appView;
-  public final MainDexTracingResult mainDexClasses;
 
   private final Timing timing;
   private final Outliner outliner;
@@ -182,8 +180,7 @@
    * The argument `appView` is used to determine if whole program optimizations are allowed or not
    * (i.e., whether we are running R8). See {@link AppView#enableWholeProgramOptimizations()}.
    */
-  public IRConverter(
-      AppView<?> appView, Timing timing, CfgPrinter printer, MainDexTracingResult mainDexClasses) {
+  public IRConverter(AppView<?> appView, Timing timing, CfgPrinter printer) {
     assert appView.appInfo().hasLiveness() || appView.graphLens().isIdentityLens();
     assert appView.options() != null;
     assert appView.options().programConsumer != null;
@@ -192,7 +189,6 @@
     this.appView = appView;
     this.options = appView.options();
     this.printer = printer;
-    this.mainDexClasses = mainDexClasses;
     this.codeRewriter = new CodeRewriter(appView, this);
     this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
     this.classInitializerDefaultsOptimization =
@@ -303,7 +299,7 @@
               : null;
       this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
       this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
-      this.inliner = new Inliner(appViewWithLiveness, mainDexClasses, lensCodeRewriter);
+      this.inliner = new Inliner(appViewWithLiveness, lensCodeRewriter);
       this.outliner = new Outliner(appViewWithLiveness);
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
@@ -315,9 +311,7 @@
         this.identifierNameStringMarker = null;
       }
       this.devirtualizer =
-          options.enableDevirtualization
-              ? new Devirtualizer(appViewWithLiveness, mainDexClasses)
-              : null;
+          options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null;
       this.typeChecker = new TypeChecker(appViewWithLiveness, VerifyTypesHelper.create(appView));
       this.d8NestBasedAccessDesugaring = null;
       this.serviceLoaderRewriter =
@@ -364,16 +358,11 @@
 
   /** Create an IR converter for processing methods with full program optimization disabled. */
   public IRConverter(AppView<?> appView, Timing timing) {
-    this(appView, timing, null, MainDexTracingResult.NONE);
-  }
-
-  /** Create an IR converter for processing methods with full program optimization disabled. */
-  public IRConverter(AppView<?> appView, Timing timing, CfgPrinter printer) {
-    this(appView, timing, printer, MainDexTracingResult.NONE);
+    this(appView, timing, null);
   }
 
   public IRConverter(AppInfo appInfo, Timing timing, CfgPrinter printer) {
-    this(AppView.createForD8(appInfo), timing, printer, MainDexTracingResult.NONE);
+    this(AppView.createForD8(appInfo), timing, printer);
   }
 
   private void removeLambdaDeserializationMethods() {
@@ -486,7 +475,7 @@
       appView.setAppInfo(
           new AppInfo(
               appView.appInfo().getSyntheticItems().commit(application),
-              appView.appInfo().getMainDexClasses()));
+              appView.appInfo().getMainDexInfo()));
       application = appView.appInfo().app();
     }
 
@@ -506,7 +495,7 @@
     appView.setAppInfo(
         new AppInfo(
             appView.appInfo().getSyntheticItems().commit(application),
-            appView.appInfo().getMainDexClasses()));
+            appView.appInfo().getMainDexInfo()));
   }
 
   private void convertClasses(DexApplication application, ExecutorService executorService)
@@ -1287,8 +1276,7 @@
     if (appView.appInfo().hasLiveness()) {
       // Reflection optimization 1. getClass() / forName() -> const-class
       timing.begin("Rewrite to const class");
-      ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(
-          appView.withLiveness(), code, mainDexClasses);
+      ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(appView.withLiveness(), code);
       timing.end();
     }
 
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 25f232d..db127f2 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
@@ -73,7 +73,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -716,7 +715,6 @@
     // Emulated library interfaces should generate the Emulated Library EL dispatch class.
     Map<DexType, List<DexType>> emulatedInterfacesHierarchy = processEmulatedInterfaceHierarchy();
     AppInfo appInfo = appView.appInfo();
-    MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
     for (DexType interfaceType : emulatedInterfaces.keySet()) {
       DexClass theInterface = appInfo.definitionFor(interfaceType);
       if (theInterface == null) {
@@ -728,8 +726,7 @@
                 theProgramInterface, emulatedInterfacesHierarchy);
         if (synthesizedClass != null) {
           builder.addSynthesizedClass(synthesizedClass);
-          appInfo.addSynthesizedClass(
-              synthesizedClass, mainDexClasses.contains(theProgramInterface));
+          appInfo.addSynthesizedClass(synthesizedClass, theProgramInterface);
         }
       }
     }
@@ -1159,7 +1156,6 @@
     // make original default methods abstract, remove bridge methods, create dispatch
     // classes if needed.
     AppInfo appInfo = appView.appInfo();
-    MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
     InterfaceProcessorNestedGraphLens.Builder graphLensBuilder =
         InterfaceProcessorNestedGraphLens.builder();
     Map<DexClass, DexProgramClass> classMapping =
@@ -1174,10 +1170,7 @@
           // Don't need to optimize synthesized class since all of its methods
           // are just moved from interfaces and don't need to be re-processed.
           builder.addSynthesizedClass(synthesizedClass);
-          boolean addToMainDexClasses =
-              interfaceClass.isProgramClass()
-                  && mainDexClasses.contains(interfaceClass.asProgramClass());
-          appInfo.addSynthesizedClass(synthesizedClass, addToMainDexClasses);
+          appInfo.addSynthesizedClass(synthesizedClass, interfaceClass.asProgramClass());
         });
     new InterfaceMethodRewriterFixup(appView, graphLens).run();
     if (appView.options().isDesugaredLibraryCompilation()) {
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 79f36ad..969b5d5 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
@@ -38,7 +38,6 @@
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexDirectReferenceTracer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -199,25 +198,17 @@
     // Don't inline code with references beyond root main dex classes into a root main dex class.
     // If we do this it can increase the size of the main dex dependent classes.
     if (reason != Reason.FORCE
-        && inlineeRefersToClassesNotInMainDex(method.getHolderType(), singleTarget)) {
+        && inliner.mainDexInfo.disallowInliningIntoContext(
+            appView.appInfo(), method, singleTarget)) {
       whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
       return false;
     }
     assert reason != Reason.FORCE
-            || !inlineeRefersToClassesNotInMainDex(method.getHolderType(), singleTarget)
-        : MainDexDirectReferenceTracer.getFirstReferenceOutsideFromCode(
-            appView.appInfo(), singleTarget, inliner.mainDexClasses.getRoots());
+        || !inliner.mainDexInfo.disallowInliningIntoContext(
+            appView.appInfo(), method, singleTarget);
     return true;
   }
 
-  private boolean inlineeRefersToClassesNotInMainDex(DexType holder, ProgramMethod target) {
-    if (inliner.mainDexClasses.isEmpty() || !inliner.mainDexClasses.getRoots().contains(holder)) {
-      return false;
-    }
-    return MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
-        appView.appInfo(), target, inliner.mainDexClasses.getRoots());
-  }
-
   private boolean satisfiesRequirementsForSimpleInlining(
       InvokeMethod invoke, ProgramMethod target) {
     // If we are looking for a simple method, only inline if actually simple.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 7179327..f5f4d70 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -48,13 +47,10 @@
 public class Devirtualizer {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final MainDexTracingResult mainDexTracingResult;
   private final InternalOptions options;
 
-  public Devirtualizer(
-      AppView<AppInfoWithLiveness> appView, MainDexTracingResult mainDexTracingResult) {
+  public Devirtualizer(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.mainDexTracingResult = mainDexTracingResult;
     this.options = appView.options();
   }
 
@@ -154,7 +150,7 @@
           DexClassAndMethod reboundTarget = rebindSuperInvokeToMostSpecific(invokedMethod, context);
           if (reboundTarget != null
               && reboundTarget.getReference() != invokedMethod
-              && !isRebindingNewClassIntoMainDex(invokedMethod, reboundTarget.getReference())) {
+              && !isRebindingNewClassIntoMainDex(context, reboundTarget.getReference())) {
             it.replaceCurrentInstruction(
                 new InvokeSuper(
                     reboundTarget.getReference(),
@@ -197,7 +193,7 @@
         }
 
         // Ensure that we are not adding a new main dex root
-        if (isRebindingNewClassIntoMainDex(invoke.getInvokedMethod(), target.getReference())) {
+        if (isRebindingNewClassIntoMainDex(context, target.getReference())) {
           continue;
         }
 
@@ -394,13 +390,7 @@
     return newResolutionResult.getResolvedMethod().method;
   }
 
-  private boolean isRebindingNewClassIntoMainDex(
-      DexMethod originalMethod, DexMethod reboundMethod) {
-    if (!mainDexTracingResult.isRoot(originalMethod.holder)
-        && !appView.appInfo().getMainDexClasses().contains(originalMethod.holder)) {
-      return false;
-    }
-    return !mainDexTracingResult.isRoot(reboundMethod.holder)
-        && !appView.appInfo().getMainDexClasses().contains(reboundMethod.holder);
+  private boolean isRebindingNewClassIntoMainDex(ProgramMethod context, DexMethod reboundMethod) {
+    return !appView.appInfo().getMainDexInfo().canRebindReference(context, reboundMethod);
   }
 }
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 9e726b9..e034587 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
@@ -59,7 +59,7 @@
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexTracingResult;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
@@ -84,7 +84,7 @@
   protected final AppView<AppInfoWithLiveness> appView;
   private final Set<DexMethod> extraNeverInlineMethods;
   private final LensCodeRewriter lensCodeRewriter;
-  final MainDexTracingResult mainDexClasses;
+  final MainDexInfo mainDexInfo;
 
   // State for inlining methods which are known to be called twice.
   private boolean applyDoubleInlining = false;
@@ -97,7 +97,6 @@
 
   public Inliner(
       AppView<AppInfoWithLiveness> appView,
-      MainDexTracingResult mainDexClasses,
       LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
@@ -106,7 +105,7 @@
             ? ImmutableSet.of()
             : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
     this.lensCodeRewriter = lensCodeRewriter;
-    this.mainDexClasses = mainDexClasses;
+    this.mainDexInfo = appView.appInfo().getMainDexInfo();
     availableApiExceptions =
         appView.options().canHaveDalvikCatchHandlerVerificationBug()
             ? new AvailableApiExceptions(appView.options())
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 cc5e7ed..afdf56a 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
@@ -26,7 +26,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.Sets;
 import java.util.Set;
@@ -37,7 +36,7 @@
   // Rewrite getClass() to const-class if the type of the given instance is effectively final.
   // Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
   public static void rewriteGetClassOrForNameToConstClass(
-      AppView<AppInfoWithLiveness> appView, IRCode code, MainDexTracingResult mainDexClasses) {
+      AppView<AppInfoWithLiveness> appView, IRCode code) {
     if (!appView.appInfo().canUseConstClassInstructions(appView.options())) {
       return;
     }
@@ -62,14 +61,14 @@
               context,
               invoke.asInvokeStatic(),
               rewriteSingleGetClassOrForNameToConstClass(
-                  appView, code, it, invoke, affectedValues, mainDexClasses));
+                  appView, code, it, invoke, affectedValues));
         } else {
           applyTypeForGetClassTo(
               appView,
               context,
               invoke.asInvokeVirtual(),
               rewriteSingleGetClassOrForNameToConstClass(
-                  appView, code, it, invoke, affectedValues, mainDexClasses));
+                  appView, code, it, invoke, affectedValues));
         }
       }
     }
@@ -85,14 +84,15 @@
       IRCode code,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
-      Set<Value> affectedValues,
-      MainDexTracingResult mainDexClasses) {
+      Set<Value> affectedValues) {
     return (type, baseClass) -> {
       if (invoke.getInvokedMethod().match(appView.dexItemFactory().classMethods.forName)) {
         // Bail-out if the optimization could increase the size of the main dex.
         if (baseClass.isProgramClass()
-            && !mainDexClasses.canReferenceItemFromContextWithoutIncreasingMainDexSize(
-                baseClass.asProgramClass(), code.context())) {
+            && !appView
+                .appInfo()
+                .getMainDexInfo()
+                .canRebindReference(code.context(), baseClass.getType())) {
           return;
         }
 
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
index 5a8ae19..48e545c 100644
--- 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
+import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
@@ -32,6 +34,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public class UnboxedEnumMemberRelocator {
 
@@ -129,9 +132,8 @@
         Set<DexProgramClass> relocatedEnums,
         DirectMappedDexApplication.Builder appBuilder,
         FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      DexType deterministicContextType = findDeterministicContextType(contexts);
-      assert deterministicContextType.isClassType();
-      String descriptorString = deterministicContextType.toDescriptorString();
+      DexProgramClass deterministicContext = findDeterministicContextType(contexts, alwaysTrue());
+      String descriptorString = deterministicContext.getType().toDescriptorString();
       String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
       String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
       DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
@@ -180,20 +182,25 @@
               appView.dexItemFactory().getSkipNameValidationForTesting(),
               DexProgramClass::checksumFromType);
       appBuilder.addSynthesizedClass(syntheticClass);
+      MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
       appView
           .appInfo()
           .addSynthesizedClass(
-              syntheticClass, appView.appInfo().getMainDexClasses().containsAnyOf(contexts));
+              syntheticClass, findDeterministicContextType(contexts, mainDexInfo::isMainDex));
       return syntheticClass;
     }
 
-    private DexType findDeterministicContextType(Set<DexProgramClass> contexts) {
-      DexType deterministicContext = null;
+    private DexProgramClass findDeterministicContextType(
+        Set<DexProgramClass> contexts, Predicate<DexProgramClass> predicate) {
+      DexProgramClass deterministicContext = null;
       for (DexProgramClass context : contexts) {
+        if (!predicate.test(context)) {
+          continue;
+        }
         if (deterministicContext == null) {
-          deterministicContext = context.type;
-        } else if (context.type.compareTo(deterministicContext) < 0) {
-          deterministicContext = context.type;
+          deterministicContext = context;
+        } else if (context.type.compareTo(deterministicContext.type) < 0) {
+          deterministicContext = context;
         }
       }
       return deterministicContext;
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 baa1189..06c03e7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -195,7 +195,7 @@
   AppInfoWithLiveness(
       CommittedItems syntheticItems,
       ClassToFeatureSplitMap classToFeatureSplitMap,
-      MainDexClasses mainDexClasses,
+      MainDexInfo mainDexInfo,
       Set<DexType> deadProtoTypes,
       MissingClasses missingClasses,
       Set<DexType> liveTypes,
@@ -234,7 +234,7 @@
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Set<DexType> lockCandidates,
       Map<DexType, Visibility> initClassReferences) {
-    super(syntheticItems, classToFeatureSplitMap, mainDexClasses, missingClasses);
+    super(syntheticItems, classToFeatureSplitMap, mainDexInfo, missingClasses);
     this.deadProtoTypes = deadProtoTypes;
     this.liveTypes = liveTypes;
     this.targetedMethods = targetedMethods;
@@ -279,7 +279,7 @@
     this(
         committedItems,
         previous.getClassToFeatureSplitMap(),
-        previous.getMainDexClasses(),
+        previous.getMainDexInfo(),
         previous.deadProtoTypes,
         previous.getMissingClasses(),
         CollectionUtils.mergeSets(previous.liveTypes, committedItems.getCommittedProgramTypes()),
@@ -324,7 +324,7 @@
     this(
         previous.getSyntheticItems().commitPrunedItems(prunedItems),
         previous.getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
-        previous.getMainDexClasses().withoutPrunedItems(prunedItems),
+        previous.getMainDexInfo().withoutPrunedItems(prunedItems),
         previous.deadProtoTypes,
         previous.getMissingClasses(),
         prunedItems.hasRemovedClasses()
@@ -376,6 +376,52 @@
     return true;
   }
 
+  @Override
+  public AppInfoWithLiveness rebuildWithMainDexInfo(MainDexInfo mainDexInfo) {
+    return new AppInfoWithLiveness(
+        getSyntheticItems().commit(app()),
+        getClassToFeatureSplitMap(),
+        mainDexInfo,
+        deadProtoTypes,
+        getMissingClasses(),
+        liveTypes,
+        targetedMethods,
+        failedMethodResolutionTargets,
+        failedFieldResolutionTargets,
+        bootstrapMethods,
+        methodsTargetedByInvokeDynamic,
+        virtualMethodsTargetedByInvokeDirect,
+        liveMethods,
+        fieldAccessInfoCollection,
+        methodAccessInfoCollection,
+        objectAllocationInfoCollection,
+        callSites,
+        keepInfo,
+        mayHaveSideEffects,
+        noSideEffects,
+        assumedValues,
+        alwaysInline,
+        forceInline,
+        neverInline,
+        neverInlineDueToSingleCaller,
+        whyAreYouNotInlining,
+        keepConstantArguments,
+        keepUnusedArguments,
+        reprocess,
+        neverReprocess,
+        alwaysClassInline,
+        neverClassInline,
+        noClassMerging,
+        noVerticalClassMerging,
+        noHorizontalClassMerging,
+        neverPropagateValue,
+        identifierNameStrings,
+        prunedTypes,
+        switchMaps,
+        lockCandidates,
+        initClassReferences);
+  }
+
   private static KeepInfoCollection extendPinnedItems(
       AppInfoWithLiveness previous, Collection<? extends DexReference> additionalPinnedItems) {
     if (additionalPinnedItems == null || additionalPinnedItems.isEmpty()) {
@@ -418,7 +464,7 @@
     super(
         previous.getSyntheticItems().commit(previous.app()),
         previous.getClassToFeatureSplitMap(),
-        previous.getMainDexClasses(),
+        previous.getMainDexInfo(),
         previous.getMissingClasses());
     this.deadProtoTypes = previous.deadProtoTypes;
     this.liveTypes = previous.liveTypes;
@@ -999,7 +1045,7 @@
     return new AppInfoWithLiveness(
         committedItems,
         getClassToFeatureSplitMap().rewrittenWithLens(lens),
-        getMainDexClasses().rewrittenWithLens(lens),
+        getMainDexInfo().rewrittenWithLens(lens),
         deadProtoTypes,
         getMissingClasses().commitSyntheticItems(committedItems),
         lens.rewriteTypes(liveTypes),
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 d734eb5..9c672b1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -393,15 +393,13 @@
   private final Map<DexMethod, ProgramMethod> methodsWithTwrCloseResource = new IdentityHashMap<>();
   private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
   private final ProgramMethodSet pendingDesugaring = ProgramMethodSet.create();
-  private final MainDexTracingResult previousMainDexTracingResult;
 
   Enqueuer(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
-      Mode mode,
-      MainDexTracingResult previousMainDexTracingResult) {
+      Mode mode) {
     assert appView.appServices() != null;
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
@@ -419,7 +417,6 @@
         mode.isInitialTreeShaking() && options.forceProguardCompatibility
             ? ProguardCompatibilityActions.builder()
             : null;
-    this.previousMainDexTracingResult = previousMainDexTracingResult;
 
     if (mode.isInitialOrFinalTreeShaking()) {
       if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
@@ -1746,7 +1743,7 @@
 
     assert !mode.isFinalMainDexTracing()
             || !options.testing.checkForNotExpandingMainDexTracingResult
-            || previousMainDexTracingResult.isRoot(clazz)
+            || appView.appInfo().getMainDexInfo().isTracedRoot(clazz)
             || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
@@ -3023,7 +3020,7 @@
   }
 
   // Returns the set of live types.
-  public Set<DexProgramClass> traceMainDex(ExecutorService executorService, Timing timing)
+  public MainDexInfo traceMainDex(ExecutorService executorService, Timing timing)
       throws ExecutionException {
     assert analyses.isEmpty();
     assert mode.isMainDexTracing();
@@ -3032,7 +3029,12 @@
     enqueueRootItems(rootSet.noShrinking);
     trace(executorService, timing);
     options.reporter.failIfPendingErrors();
-    return liveTypes.getItems();
+    // Calculate the automatic main dex list according to legacy multidex constraints.
+    MainDexInfo.Builder builder = MainDexInfo.builder();
+    liveTypes.getItems().forEach(builder::addRoot);
+    new MainDexListBuilder(appView, builder.getRoots(), builder).run();
+    MainDexInfo previousMainDexInfo = appInfo.getMainDexInfo();
+    return builder.build(previousMainDexInfo);
   }
 
   public AppInfoWithLiveness traceApplication(
@@ -3210,9 +3212,9 @@
       appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
     }
 
-    void amendMainDexClasses(MainDexClasses mainDexClasses) {
+    void amendMainDexClasses(MainDexInfo mainDexInfo) {
       assert !isEmpty();
-      mainDexClasses.addAll(mainDexTypes);
+      mainDexTypes.forEach(mainDexInfo::addSyntheticClass);
     }
 
     void enqueueWorkItems(Enqueuer enqueuer) {
@@ -3296,7 +3298,7 @@
               additions.amendApplication(appBuilder);
               return appBuilder.build();
             });
-    additions.amendMainDexClasses(appInfo.getMainDexClasses());
+    additions.amendMainDexClasses(appInfo.getMainDexInfo());
     appView.setAppInfo(appInfo);
     subtypingInfo = new SubtypingInfo(appView);
 
@@ -3490,7 +3492,7 @@
         new AppInfoWithLiveness(
             appInfo.getSyntheticItems().commit(app),
             appInfo.getClassToFeatureSplitMap(),
-            appInfo.getMainDexClasses(),
+            appInfo.getMainDexInfo(),
             deadProtoTypes,
             appView.testing().enableExperimentalMissingClassesReporting
                 ? missingClassesBuilder.reportMissingClasses(appView)
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 04fb840..7636fe8 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -19,13 +19,7 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ExecutorService executorService,
       SubtypingInfo subtypingInfo) {
-    return new Enqueuer(
-        appView,
-        executorService,
-        subtypingInfo,
-        null,
-        Mode.INITIAL_TREE_SHAKING,
-        MainDexTracingResult.NONE);
+    return new Enqueuer(appView, executorService, subtypingInfo, null, Mode.INITIAL_TREE_SHAKING);
   }
 
   public static Enqueuer createForFinalTreeShaking(
@@ -36,12 +30,7 @@
       Set<DexType> initialPrunedTypes) {
     Enqueuer enqueuer =
         new Enqueuer(
-            appView,
-            executorService,
-            subtypingInfo,
-            keptGraphConsumer,
-            Mode.FINAL_TREE_SHAKING,
-            MainDexTracingResult.NONE);
+            appView, executorService, subtypingInfo, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
     enqueuer.setInitialPrunedTypes(initialPrunedTypes);
@@ -53,27 +42,16 @@
       ExecutorService executorService,
       SubtypingInfo subtypingInfo) {
     return new Enqueuer(
-        appView,
-        executorService,
-        subtypingInfo,
-        null,
-        Mode.INITIAL_MAIN_DEX_TRACING,
-        MainDexTracingResult.NONE);
+        appView, executorService, subtypingInfo, null, Mode.INITIAL_MAIN_DEX_TRACING);
   }
 
   public static Enqueuer createForFinalMainDexTracing(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
-      GraphConsumer keptGraphConsumer,
-      MainDexTracingResult previousMainDexTracingResult) {
+      GraphConsumer keptGraphConsumer) {
     return new Enqueuer(
-        appView,
-        executorService,
-        subtypingInfo,
-        keptGraphConsumer,
-        Mode.FINAL_MAIN_DEX_TRACING,
-        previousMainDexTracingResult);
+        appView, executorService, subtypingInfo, keptGraphConsumer, Mode.FINAL_MAIN_DEX_TRACING);
   }
 
   public static Enqueuer createForWhyAreYouKeeping(
@@ -82,11 +60,6 @@
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer) {
     return new Enqueuer(
-        appView,
-        executorService,
-        subtypingInfo,
-        keptGraphConsumer,
-        Mode.WHY_ARE_YOU_KEEPING,
-        MainDexTracingResult.NONE);
+        appView, executorService, subtypingInfo, keptGraphConsumer, Mode.WHY_ARE_YOU_KEEPING);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
deleted file mode 100644
index 9c1c529..0000000
--- a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
+++ /dev/null
@@ -1,118 +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.shaking;
-
-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.PrunedItems;
-import com.google.common.collect.Sets;
-import java.util.Set;
-import java.util.function.Consumer;
-
-public class MainDexClasses {
-
-  private final Set<DexType> mainDexClasses;
-
-  private MainDexClasses() {
-    this(Sets.newIdentityHashSet());
-  }
-
-  private MainDexClasses(Set<DexType> mainDexClasses) {
-    this.mainDexClasses = mainDexClasses;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static MainDexClasses createEmptyMainDexClasses() {
-    return new MainDexClasses();
-  }
-
-  public void add(DexProgramClass clazz) {
-    mainDexClasses.add(clazz.getType());
-  }
-
-  public void addAll(MainDexClasses other) {
-    mainDexClasses.addAll(other.mainDexClasses);
-  }
-
-  public void addAll(MainDexTracingResult other) {
-    mainDexClasses.addAll(other.getClasses());
-  }
-
-  public void addAll(Iterable<DexProgramClass> classes) {
-    for (DexProgramClass clazz : classes) {
-      add(clazz);
-    }
-  }
-
-  public boolean contains(DexType type) {
-    return mainDexClasses.contains(type);
-  }
-
-  public boolean contains(DexProgramClass clazz) {
-    return contains(clazz.getType());
-  }
-
-  public boolean containsAnyOf(Iterable<DexProgramClass> classes) {
-    for (DexProgramClass clazz : classes) {
-      if (contains(clazz)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public void forEach(Consumer<DexType> fn) {
-    mainDexClasses.forEach(fn);
-  }
-
-  public boolean isEmpty() {
-    return mainDexClasses.isEmpty();
-  }
-
-  public MainDexClasses rewrittenWithLens(GraphLens lens) {
-    MainDexClasses rewrittenMainDexClasses = createEmptyMainDexClasses();
-    for (DexType mainDexClass : mainDexClasses) {
-      DexType rewrittenMainDexClass = lens.lookupType(mainDexClass);
-      rewrittenMainDexClasses.mainDexClasses.add(rewrittenMainDexClass);
-    }
-    return rewrittenMainDexClasses;
-  }
-
-  public int size() {
-    return mainDexClasses.size();
-  }
-
-  public MainDexClasses withoutPrunedItems(PrunedItems prunedItems) {
-    if (prunedItems.isEmpty()) {
-      return this;
-    }
-    MainDexClasses mainDexClassesAfterPruning = createEmptyMainDexClasses();
-    for (DexType mainDexClass : mainDexClasses) {
-      if (!prunedItems.getRemovedClasses().contains(mainDexClass)) {
-        mainDexClassesAfterPruning.mainDexClasses.add(mainDexClass);
-      }
-    }
-    return mainDexClassesAfterPruning;
-  }
-
-  public static class Builder {
-
-    private final Set<DexType> mainDexClasses = Sets.newIdentityHashSet();
-
-    private Builder() {}
-
-    public void add(DexProgramClass clazz) {
-      mainDexClasses.add(clazz.getType());
-    }
-
-    public MainDexClasses build() {
-      return new MainDexClasses(mainDexClasses);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 6f7cade..459dea6 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.Box;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class MainDexDirectReferenceTracer {
   private final AnnotationDirectReferenceCollector annotationDirectReferenceCollector =
@@ -66,19 +67,19 @@
     method.registerCodeReferences(codeDirectReferenceCollector);
   }
 
-  public static boolean hasReferencesOutsideFromCode(
-      AppInfoWithClassHierarchy appInfo, ProgramMethod method, Set<DexType> classes) {
-    return getFirstReferenceOutsideFromCode(appInfo, method, classes) != null;
+  public static boolean hasReferencesOutsideMainDexClasses(
+      AppInfoWithClassHierarchy appInfo, ProgramMethod method, Predicate<DexType> isOutside) {
+    return getFirstReferenceOutsideFromCode(appInfo, method, isOutside) != null;
   }
 
   public static DexProgramClass getFirstReferenceOutsideFromCode(
-      AppInfoWithClassHierarchy appInfo, ProgramMethod method, Set<DexType> classes) {
+      AppInfoWithClassHierarchy appInfo, ProgramMethod method, Predicate<DexType> isOutside) {
     Box<DexProgramClass> result = new Box<>();
     new MainDexDirectReferenceTracer(
             appInfo,
             type -> {
               DexType baseType = type.toBaseType(appInfo.dexItemFactory());
-              if (baseType.isClassType() && !classes.contains(baseType)) {
+              if (baseType.isClassType() && isOutside.test(baseType)) {
                 DexClass cls = appInfo.definitionFor(baseType);
                 if (cls != null && cls.isProgramClass()) {
                   result.set(cls.asProgramClass());
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
new file mode 100644
index 0000000..8d793aa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -0,0 +1,382 @@
+// 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 static com.android.tools.r8.shaking.MainDexInfo.MainDexGroup.MAIN_DEX_ROOT;
+import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+public class MainDexInfo {
+
+  private static final MainDexInfo NONE = new MainDexInfo(Sets.newIdentityHashSet());
+
+  public enum MainDexGroup {
+    MAIN_DEX_LIST,
+    MAIN_DEX_ROOT,
+    MAIN_DEX_DEPENDENCY,
+    NOT_IN_MAIN_DEX
+  }
+
+  // Specific set of classes specified to be in main-dex
+  private final Set<DexType> classList;
+  private final Map<DexType, DexType> synthesizedClassesMap;
+  // Traced roots are traced main dex references.
+  private final Set<DexType> tracedRoots;
+  // Traced dependencies are those classes that are directly referenced from traced roots, but will
+  // not be loaded before loading the remaining dex files.
+  private final Set<DexType> tracedDependencies;
+
+  private MainDexInfo(Set<DexType> classList) {
+    this(
+        classList, Sets.newIdentityHashSet(), Sets.newIdentityHashSet(), Sets.newIdentityHashSet());
+  }
+
+  private MainDexInfo(
+      Set<DexType> classList,
+      Set<DexType> tracedRoots,
+      Set<DexType> tracedDependencies,
+      Set<DexType> synthesizedClasses) {
+    this.classList = classList;
+    this.tracedRoots = tracedRoots;
+    this.tracedDependencies = tracedDependencies;
+    this.synthesizedClassesMap = new ConcurrentHashMap<>();
+    synthesizedClasses.forEach(type -> synthesizedClassesMap.put(type, type));
+    assert tracedDependencies.stream().noneMatch(tracedRoots::contains);
+    assert tracedRoots.containsAll(synthesizedClasses);
+  }
+
+  public boolean isNone() {
+    assert none() == NONE;
+    return this == NONE;
+  }
+
+  public boolean isMainDex(ProgramDefinition definition) {
+    return isFromList(definition) || isTracedRoot(definition) || isDependency(definition);
+  }
+
+  public boolean isFromList(ProgramDefinition definition) {
+    return isFromList(definition.getContextType());
+  }
+
+  private boolean isFromList(DexReference reference) {
+    return classList.contains(reference.getContextType());
+  }
+
+  public boolean isTracedRoot(ProgramDefinition definition) {
+    return isTracedRoot(definition.getContextType());
+  }
+
+  private boolean isTracedRoot(DexReference reference) {
+    return tracedRoots.contains(reference.getContextType());
+  }
+
+  private boolean isDependency(ProgramDefinition definition) {
+    return isDependency(definition.getContextType());
+  }
+
+  private boolean isDependency(DexReference reference) {
+    return tracedDependencies.contains(reference.getContextType());
+  }
+
+  public boolean canRebindReference(ProgramMethod context, DexReference referenceToTarget) {
+    MainDexGroup holderGroup = getMainDexGroupInternal(context);
+    if (holderGroup == MainDexGroup.NOT_IN_MAIN_DEX
+        || holderGroup == MainDexGroup.MAIN_DEX_DEPENDENCY) {
+      // We are always free to rebind/inline into something not in main-dex or traced dependencies.
+      return true;
+    }
+    if (holderGroup == MainDexGroup.MAIN_DEX_LIST) {
+      // If the holder is in the class list, we are not allowed to make any assumptions on the
+      // holder
+      // being a root or a dependency. Therefore we cannot merge.
+      return false;
+    }
+    assert holderGroup == MAIN_DEX_ROOT;
+    // Otherwise we allow if either is both root.
+    return getMainDexGroupInternal(referenceToTarget) == MAIN_DEX_ROOT;
+  }
+
+  public boolean canMerge(ProgramDefinition candidate) {
+    return !isFromList(candidate);
+  }
+
+  public boolean canMerge(ProgramDefinition source, ProgramDefinition target) {
+    return canMerge(source.getContextType(), target.getContextType());
+  }
+
+  private boolean canMerge(DexReference source, DexReference target) {
+    MainDexGroup sourceGroup = getMainDexGroupInternal(source);
+    MainDexGroup targetGroup = getMainDexGroupInternal(target);
+    if (sourceGroup != targetGroup) {
+      return false;
+    }
+    // If the holder is in the class list, we are not allowed to make any assumptions on the holder
+    // being a root or a dependency. Therefore we cannot merge.
+    return sourceGroup != MainDexGroup.MAIN_DEX_LIST;
+  }
+
+  public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate) {
+    MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate);
+    return mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST ? null : mainDexGroupInternal;
+  }
+
+  private MainDexGroup getMainDexGroupInternal(ProgramDefinition definition) {
+    return getMainDexGroupInternal(definition.getReference());
+  }
+
+  private MainDexGroup getMainDexGroupInternal(DexReference reference) {
+    if (isFromList(reference)) {
+      return MainDexGroup.MAIN_DEX_LIST;
+    }
+    if (isTracedRoot(reference)) {
+      return MAIN_DEX_ROOT;
+    }
+    if (isDependency(reference)) {
+      return MainDexGroup.MAIN_DEX_DEPENDENCY;
+    }
+    return MainDexGroup.NOT_IN_MAIN_DEX;
+  }
+
+  public boolean disallowInliningIntoContext(
+      AppInfoWithClassHierarchy appInfo, ProgramDefinition context, ProgramMethod method) {
+    if (context.getContextType() == method.getContextType()) {
+      return false;
+    }
+    MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context);
+    if (mainDexGroupInternal == MainDexGroup.NOT_IN_MAIN_DEX
+        || mainDexGroupInternal == MainDexGroup.MAIN_DEX_DEPENDENCY) {
+      return false;
+    }
+    if (mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST) {
+      return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses(
+          appInfo, method, not(this::isFromList));
+    }
+    assert mainDexGroupInternal == MAIN_DEX_ROOT;
+    return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses(
+        appInfo, method, not(this::isTracedRoot));
+  }
+
+  public boolean isEmpty() {
+    assert !tracedRoots.isEmpty() || tracedDependencies.isEmpty();
+    return tracedRoots.isEmpty() && classList.isEmpty();
+  }
+
+  public static MainDexInfo none() {
+    return NONE;
+  }
+
+  // TODO(b/178127572): This mutates the MainDexClasses which otherwise should be immutable.
+  public void addSyntheticClass(DexProgramClass clazz) {
+    // TODO(b/178127572): This will add a synthesized type as long as the initial set is not empty.
+    //  A better approach would be to use the context for the synthetic with a containment check.
+    assert !isNone();
+    if (!classList.isEmpty()) {
+      synthesizedClassesMap.computeIfAbsent(
+          clazz.type,
+          type -> {
+            classList.add(type);
+            return type;
+          });
+    }
+    if (!tracedRoots.isEmpty()) {
+      synthesizedClassesMap.computeIfAbsent(
+          clazz.type,
+          type -> {
+            tracedRoots.add(type);
+            return type;
+          });
+    }
+  }
+
+  public void addLegacySyntheticClass(DexProgramClass clazz, ProgramDefinition context) {
+    if (isTracedRoot(context) || isFromList(context) || isDependency(context)) {
+      addSyntheticClass(clazz);
+    }
+  }
+
+  public int size() {
+    return classList.size() + tracedRoots.size() + tracedDependencies.size();
+  }
+
+  public void forEachExcludingDependencies(Consumer<DexType> fn) {
+    // Prevent seeing duplicates in the list and roots.
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    classList.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen));
+    tracedRoots.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen));
+  }
+
+  public void forEach(Consumer<DexType> fn) {
+    // Prevent seeing duplicates in the list and roots.
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    classList.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen));
+    tracedRoots.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen));
+    tracedDependencies.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen));
+  }
+
+  public MainDexInfo withoutPrunedItems(PrunedItems prunedItems) {
+    if (prunedItems.isEmpty()) {
+      return this;
+    }
+    Set<DexType> removedClasses = prunedItems.getRemovedClasses();
+    Set<DexType> modifiedClassList = Sets.newIdentityHashSet();
+    Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet();
+    classList.forEach(type -> ifNotRemoved(type, removedClasses, modifiedClassList::add));
+    synthesizedClassesMap
+        .keySet()
+        .forEach(type -> ifNotRemoved(type, removedClasses, modifiedSynthesized::add));
+    MainDexInfo.Builder builder = builder();
+    tracedRoots.forEach(type -> ifNotRemoved(type, removedClasses, builder::addRoot));
+    tracedDependencies.forEach(type -> ifNotRemoved(type, removedClasses, builder::addDependency));
+    return builder.build(modifiedClassList, modifiedSynthesized);
+  }
+
+  private void ifNotRemoved(
+      DexType type, Set<DexType> removedClasses, Consumer<DexType> notRemoved) {
+    if (!removedClasses.contains(type)) {
+      notRemoved.accept(type);
+    }
+  }
+
+  public MainDexInfo rewrittenWithLens(GraphLens lens) {
+    Set<DexType> modifiedClassList = Sets.newIdentityHashSet();
+    Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet();
+    classList.forEach(
+        type -> rewriteAndApplyIfNotPrimitiveType(lens, type, modifiedClassList::add));
+    synthesizedClassesMap.keySet().forEach(type -> modifiedSynthesized.add(lens.lookupType(type)));
+    MainDexInfo.Builder builder = builder();
+    tracedRoots.forEach(type -> rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addRoot));
+    tracedDependencies.forEach(
+        type -> {
+          if (lens.isSyntheticFinalizationGraphLens()) {
+            // Synthetic finalization is allowed to merge identical classes into the same class. The
+            // rewritten type of a traced dependency can therefore be finalized with a traced root.
+            rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependencyIfNotRoot);
+          } else {
+            rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependency);
+          }
+        });
+    return builder.build(modifiedClassList, modifiedSynthesized);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static MainDexInfo createEmptyMainDexClasses() {
+    return new MainDexInfo(
+        Sets.newIdentityHashSet(),
+        Sets.newIdentityHashSet(),
+        Sets.newIdentityHashSet(),
+        Sets.newIdentityHashSet());
+  }
+
+  public static class Builder {
+
+    private final Set<DexType> list = Sets.newIdentityHashSet();
+    private final Set<DexType> roots = Sets.newIdentityHashSet();
+    private final Set<DexType> dependencies = Sets.newIdentityHashSet();
+
+    private Builder() {}
+
+    public void addList(DexProgramClass clazz) {
+      addList(clazz.getType());
+    }
+
+    public void addList(DexType type) {
+      list.add(type);
+    }
+
+    public void addRoot(DexProgramClass clazz) {
+      addRoot(clazz.getType());
+    }
+
+    public void addRoot(DexType type) {
+      assert !dependencies.contains(type);
+      roots.add(type);
+    }
+
+    public void addDependency(DexProgramClass clazz) {
+      addDependency(clazz.getType());
+    }
+
+    public void addDependency(DexType type) {
+      assert !roots.contains(type);
+      dependencies.add(type);
+    }
+
+    public void addDependencyIfNotRoot(DexType type) {
+      if (roots.contains(type)) {
+        return;
+      }
+      addDependency(type);
+    }
+
+    public boolean isTracedRoot(DexProgramClass clazz) {
+      return isTracedRoot(clazz.getType());
+    }
+
+    public boolean isTracedRoot(DexType type) {
+      return roots.contains(type);
+    }
+
+    public boolean isDependency(DexProgramClass clazz) {
+      return isDependency(clazz.getType());
+    }
+
+    public boolean isDependency(DexType type) {
+      return dependencies.contains(type);
+    }
+
+    public boolean contains(DexProgramClass clazz) {
+      return contains(clazz.type);
+    }
+
+    public boolean contains(DexType type) {
+      return isTracedRoot(type) || isDependency(type);
+    }
+
+    public Set<DexType> getRoots() {
+      return roots;
+    }
+
+    public MainDexInfo buildList() {
+      // When building without passing the list, the method roots and dependencies should
+      // be empty since no tracing has been done.
+      assert dependencies.isEmpty();
+      assert roots.isEmpty();
+      return new MainDexInfo(list);
+    }
+
+    public MainDexInfo build(Set<DexType> classList, Set<DexType> synthesizedClasses) {
+      // Class can contain dependencies which we should not regard as roots.
+      assert list.isEmpty();
+      return new MainDexInfo(
+          classList,
+          SetUtils.unionIdentityHashSet(roots, synthesizedClasses),
+          Sets.difference(dependencies, synthesizedClasses),
+          synthesizedClasses);
+    }
+
+    public MainDexInfo build(MainDexInfo previous) {
+      return build(previous.classList, previous.synthesizedClassesMap.keySet());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index 4fa5d3a..afb39bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -13,7 +13,6 @@
 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.utils.SetUtils;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -30,7 +29,7 @@
   private final Set<DexType> roots;
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Map<DexType, Boolean> annotationTypeContainEnum;
-  private final MainDexTracingResult.Builder mainDexClassesBuilder;
+  private final MainDexInfo.Builder mainDexInfoBuilder;
 
   public static void checkForAssumedLibraryTypes(AppInfo appInfo) {
     DexClass enumType = appInfo.definitionFor(appInfo.dexItemFactory().enumType);
@@ -50,11 +49,14 @@
    * @param appView the dex appplication.
    */
   public MainDexListBuilder(
-      Set<DexProgramClass> roots, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Set<DexType> roots,
+      MainDexInfo.Builder mainDexInfoBuilder) {
     this.appView = appView;
     // Only consider program classes for the root set.
-    this.roots = SetUtils.mapIdentityHashSet(roots, DexProgramClass::getType);
-    mainDexClassesBuilder = MainDexTracingResult.builder(appView.appInfo()).addRoots(this.roots);
+    assert roots.stream().allMatch(type -> appView.definitionFor(type).isProgramClass());
+    this.roots = roots;
+    this.mainDexInfoBuilder = mainDexInfoBuilder;
     annotationTypeContainEnum = new IdentityHashMap<>();
   }
 
@@ -62,18 +64,17 @@
     return appView.appInfo();
   }
 
-  public MainDexTracingResult run() {
+  public void run() {
     traceMainDexDirectDependencies();
     traceRuntimeAnnotationsWithEnumForMainDex();
-    return mainDexClassesBuilder.build();
   }
 
   private void traceRuntimeAnnotationsWithEnumForMainDex() {
     for (DexProgramClass clazz : appInfo().classes()) {
-      DexType dexType = clazz.type;
-      if (mainDexClassesBuilder.contains(dexType)) {
+      if (mainDexInfoBuilder.contains(clazz)) {
         continue;
       }
+      DexType dexType = clazz.type;
       if (isAnnotation(dexType) && isAnnotationWithEnum(dexType)) {
         addAnnotationsWithEnum(clazz);
         continue;
@@ -82,10 +83,11 @@
       // annotations with enums goes into the main dex, move annotated classes there as well.
       clazz.forEachAnnotation(
           annotation -> {
-            if (!mainDexClassesBuilder.contains(dexType)
+            if (!mainDexInfoBuilder.contains(clazz)
                 && annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME
                 && isAnnotationWithEnum(annotation.annotation.type)) {
-              addClassAnnotatedWithAnnotationWithEnum(dexType);
+              // Just add classes annotated with annotations with enum as direct dependencies.
+              mainDexInfoBuilder.addDependency(clazz);
             }
           });
     }
@@ -151,16 +153,10 @@
     }
   }
 
-  private void addClassAnnotatedWithAnnotationWithEnum(DexType type) {
-    // Just add classes annotated with annotations with enum ad direct dependencies.
-    addDirectDependency(type);
-  }
-
   private void addDirectDependency(DexType type) {
     // Consider only component type of arrays
     type = type.toBaseType(appView.dexItemFactory());
-
-    if (!type.isClassType() || mainDexClassesBuilder.contains(type)) {
+    if (!type.isClassType() || mainDexInfoBuilder.contains(type)) {
       return;
     }
 
@@ -173,9 +169,8 @@
   }
 
   private void addDirectDependency(DexProgramClass dexClass) {
-    DexType type = dexClass.type;
-    assert !mainDexClassesBuilder.contains(type);
-    mainDexClassesBuilder.addDependency(type);
+    assert !mainDexInfoBuilder.contains(dexClass);
+    mainDexInfoBuilder.addDependency(dexClass);
     if (dexClass.superType != null) {
       addDirectDependency(dexClass.superType);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
deleted file mode 100644
index 5bf287f..0000000
--- a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.shaking;
-
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-public class MainDexTracingResult {
-
-  public static MainDexTracingResult NONE =
-      new MainDexTracingResult(ImmutableSet.of(), ImmutableSet.of());
-
-  public static class Builder {
-    public final AppInfo appInfo;
-    public final Set<DexType> roots;
-    public final Set<DexType> dependencies;
-
-    private Builder(AppInfo appInfo) {
-      this(appInfo, Sets.newIdentityHashSet(), Sets.newIdentityHashSet());
-    }
-
-    private Builder(AppInfo appInfo, MainDexTracingResult mainDexTracingResult) {
-      this(
-          appInfo,
-          SetUtils.newIdentityHashSet(mainDexTracingResult.getRoots()),
-          SetUtils.newIdentityHashSet(mainDexTracingResult.getDependencies()));
-    }
-
-    private Builder(AppInfo appInfo, Set<DexType> roots, Set<DexType> dependencies) {
-      this.appInfo = appInfo;
-      this.roots = roots;
-      this.dependencies = dependencies;
-    }
-
-    public Builder addRoot(DexProgramClass clazz) {
-      roots.add(clazz.getType());
-      return this;
-    }
-
-    public Builder addRoot(DexType type) {
-      assert isProgramClass(type) : type.toSourceString();
-      roots.add(type);
-      return this;
-    }
-
-    public Builder addRoots(Collection<DexType> rootSet) {
-      assert rootSet.stream().allMatch(this::isProgramClass);
-      this.roots.addAll(rootSet);
-      return this;
-    }
-
-    public Builder addDependency(DexType type) {
-      assert isProgramClass(type);
-      dependencies.add(type);
-      return this;
-    }
-
-    public boolean contains(DexType type) {
-      return roots.contains(type) || dependencies.contains(type);
-    }
-
-    public MainDexTracingResult build() {
-      return new MainDexTracingResult(roots, dependencies);
-    }
-
-    private boolean isProgramClass(DexType dexType) {
-      DexClass clazz = appInfo.definitionFor(dexType);
-      return clazz != null && clazz.isProgramClass();
-    }
-  }
-
-  // The classes in the root set.
-  private final Set<DexType> roots;
-  // Additional dependencies (direct dependencies and runtime annotations with enums).
-  private final Set<DexType> dependencies;
-  // All main dex classes.
-  private final Set<DexType> classes;
-
-  private MainDexTracingResult(Set<DexType> roots, Set<DexType> dependencies) {
-    assert Sets.intersection(roots, dependencies).isEmpty();
-    this.roots = Collections.unmodifiableSet(roots);
-    this.dependencies = Collections.unmodifiableSet(dependencies);
-    this.classes = Sets.union(roots, dependencies);
-  }
-
-  public boolean canReferenceItemFromContextWithoutIncreasingMainDexSize(
-      ProgramDefinition item, ProgramDefinition context) {
-    // If the context is not a root, then additional references from inside the context will not
-    // increase the size of the main dex.
-    if (!isRoot(context)) {
-      return true;
-    }
-    // Otherwise, require that the item is a root itself.
-    return isRoot(item);
-  }
-
-  public boolean isEmpty() {
-    assert !roots.isEmpty() || dependencies.isEmpty();
-    return roots.isEmpty();
-  }
-
-  public Set<DexType> getRoots() {
-    return roots;
-  }
-
-  public Set<DexType> getDependencies() {
-    return dependencies;
-  }
-
-  public Set<DexType> getClasses() {
-    return classes;
-  }
-
-  public boolean contains(ProgramDefinition clazz) {
-    return contains(clazz.getContextType());
-  }
-
-  public boolean contains(DexType type) {
-    return getClasses().contains(type);
-  }
-
-  private void collectTypesMatching(
-      Set<DexType> types, Predicate<DexType> predicate, Consumer<DexType> consumer) {
-    types.forEach(
-        type -> {
-          if (predicate.test(type)) {
-            consumer.accept(type);
-          }
-        });
-  }
-
-  public boolean isRoot(ProgramDefinition definition) {
-    return getRoots().contains(definition.getContextType());
-  }
-
-  public boolean isRoot(DexType type) {
-    return getRoots().contains(type);
-  }
-
-  public boolean isDependency(ProgramDefinition definition) {
-    return getDependencies().contains(definition.getContextType());
-  }
-
-  public MainDexTracingResult prunedCopy(AppInfoWithLiveness appInfo) {
-    Builder builder = builder(appInfo);
-    Predicate<DexType> wasPruned = appInfo::wasPruned;
-    collectTypesMatching(roots, wasPruned.negate(), builder::addRoot);
-    collectTypesMatching(dependencies, wasPruned.negate(), builder::addDependency);
-    return builder.build();
-  }
-
-  public static Builder builder(AppInfo appInfo) {
-    return new Builder(appInfo);
-  }
-
-  public Builder extensionBuilder(AppInfo appInfo) {
-    return new Builder(appInfo, this);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 3d51c1a..8848ece 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
@@ -2386,15 +2387,4 @@
           delayedRootSetActionItems);
     }
   }
-
-  private static void rewriteAndApplyIfNotPrimitiveType(
-      GraphLens graphLens, DexReference reference, Consumer<DexReference> rewrittenConsumer) {
-    DexReference rewrittenReference = graphLens.rewriteReference(reference);
-    // Enum unboxing can change a class type to int which leads to errors going forward since
-    // the root set should not have primitive types.
-    if (rewrittenReference.isDexType() && rewrittenReference.asDexType().isPrimitiveType()) {
-      return;
-    }
-    rewrittenConsumer.accept(rewrittenReference);
-  }
 }
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 c33cf70..4963f21 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -219,23 +219,22 @@
   // All the bridge methods that have been synthesized during vertical class merging.
   private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
 
-  private final MainDexTracingResult mainDexClasses;
+  private final MainDexInfo mainDexInfo;
 
   public VerticalClassMerger(
       DexApplication application,
       AppView<AppInfoWithLiveness> appView,
       ExecutorService executorService,
-      Timing timing,
-      MainDexTracingResult mainDexClasses) {
+      Timing timing) {
     this.application = application;
     this.appInfo = appView.appInfo();
     this.appView = appView;
+    this.mainDexInfo = appInfo.getMainDexInfo();
     this.subtypingInfo = appInfo.computeSubtypingInfo();
     this.executorService = executorService;
     this.methodPoolCollection = new MethodPoolCollection(appView, subtypingInfo);
     this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView.dexItemFactory());
     this.timing = timing;
-    this.mainDexClasses = mainDexClasses;
 
     Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
     initializePinnedTypes(classes); // Must be initialized prior to mergeCandidates.
@@ -826,20 +825,8 @@
       return;
     }
 
-    // For a main dex class in the dependent set only merge with other classes in either main dex
-    // set.
-    if ((mainDexClasses.getDependencies().contains(clazz.type)
-            || mainDexClasses.getDependencies().contains(targetClass.type))
-        && !(mainDexClasses.getClasses().contains(clazz.type)
-            && mainDexClasses.getClasses().contains(targetClass.type))) {
-      return;
-    }
-
-    // For a main dex class in the root set only merge with other classes in main dex root set.
-    if ((mainDexClasses.getRoots().contains(clazz.type)
-            || mainDexClasses.getRoots().contains(targetClass.type))
-        && !(mainDexClasses.getRoots().contains(clazz.type)
-            && mainDexClasses.getRoots().contains(targetClass.type))) {
+    // Check with main dex classes to see if we are allowed to merge.
+    if (!mainDexInfo.canMerge(clazz, targetClass)) {
       return;
     }
 
@@ -1670,9 +1657,7 @@
         }
         // Constructors can have references beyond the root main dex classes. This can increase the
         // size of the main dex dependent classes and we should bail out.
-        if (mainDexClasses.getRoots().contains(context.type)
-            && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
-                appView.appInfo(), method, mainDexClasses.getRoots())) {
+        if (mainDexInfo.disallowInliningIntoContext(appView.appInfo(), context, method)) {
           return AbortReason.MAIN_DEX_ROOT_OUTSIDE_REFERENCE;
         }
         return null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index f6da35b..1067e93 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import java.util.Comparator;
@@ -155,13 +155,13 @@
 
   void addIfDerivedFromMainDexClass(
       DexProgramClass externalSyntheticClass,
-      MainDexClasses mainDexClasses,
+      MainDexInfo mainDexInfo,
       Set<DexType> allMainDexTypes) {
     // The input context type (not the annotated context) determines if the derived class is to be
     // in main dex.
     // TODO(b/168584485): Once resolved allMainDexTypes == mainDexClasses.
     if (allMainDexTypes.contains(inputContextType)) {
-      mainDexClasses.add(externalSyntheticClass);
+      mainDexInfo.addSyntheticClass(externalSyntheticClass);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 2d5ec60..741831e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
@@ -86,6 +86,11 @@
       this.syntheticMethodsMap = syntheticMethodsMap;
     }
 
+    @Override
+    public boolean isSyntheticFinalizationGraphLens() {
+      return true;
+    }
+
     // The mapping is many to one, so the inverse is only defined up to equivalence groups.
     // Override the access to renamed signatures to first check for synthetic mappings before
     // using the original item mappings of the
@@ -225,7 +230,7 @@
     assert !appView.appInfo().hasClassHierarchy();
     assert !appView.appInfo().hasLiveness();
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
-    appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+    appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexInfo()));
     appView.pruneItems(result.prunedItems);
     if (result.lens != null) {
       appView.setGraphLens(result.lens);
@@ -252,7 +257,7 @@
   Result computeFinalSynthetics(AppView<?> appView) {
     assert verifyNoNestedSynthetics();
     DexApplication application;
-    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
+    MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
     Builder lensBuilder = new Builder();
     ImmutableMap.Builder<DexType, SyntheticMethodReference> finalMethodsBuilder =
         ImmutableMap.builder();
@@ -266,7 +271,6 @@
               appView,
               computeEquivalences(appView, synthetics.getNonLegacyMethods().values(), generators),
               computeEquivalences(appView, synthetics.getNonLegacyClasses().values(), generators),
-              mainDexClasses,
               lensBuilder,
               (clazz, reference) -> {
                 finalSyntheticProgramDefinitions.add(clazz);
@@ -282,13 +286,9 @@
         finalClassesBuilder.build();
 
     handleSynthesizedClassMapping(
-        finalSyntheticProgramDefinitions,
-        application,
-        options,
-        mainDexClasses,
-        lensBuilder.typeMap);
+        finalSyntheticProgramDefinitions, application, options, mainDexInfo, lensBuilder.typeMap);
 
-    assert appView.appInfo().getMainDexClasses() == mainDexClasses;
+    assert appView.appInfo().getMainDexInfo() == mainDexInfo;
 
     Set<DexType> prunedSynthetics = Sets.newIdentityHashSet();
     synthetics.forEachNonLegacyItem(
@@ -352,14 +352,13 @@
       List<DexProgramClass> finalSyntheticClasses,
       DexApplication application,
       InternalOptions options,
-      MainDexClasses mainDexClasses,
+      MainDexInfo mainDexInfo,
       Map<DexType, DexType> derivedMainDexTypesToIgnore) {
     boolean includeSynthesizedClassMappingInOutput = shouldAnnotateSynthetics(options);
     if (includeSynthesizedClassMappingInOutput) {
       updateSynthesizedClassMapping(application, finalSyntheticClasses);
     }
-    updateMainDexListWithSynthesizedClassMap(
-        application, mainDexClasses, derivedMainDexTypesToIgnore);
+    updateMainDexListWithSynthesizedClassMap(application, mainDexInfo, derivedMainDexTypesToIgnore);
     if (!includeSynthesizedClassMappingInOutput) {
       clearSynthesizedClassMapping(application);
     }
@@ -405,13 +404,13 @@
 
   private void updateMainDexListWithSynthesizedClassMap(
       DexApplication application,
-      MainDexClasses mainDexClasses,
+      MainDexInfo mainDexInfo,
       Map<DexType, DexType> derivedMainDexTypesToIgnore) {
-    if (mainDexClasses.isEmpty()) {
+    if (mainDexInfo.isEmpty()) {
       return;
     }
     List<DexProgramClass> newMainDexClasses = new ArrayList<>();
-    mainDexClasses.forEach(
+    mainDexInfo.forEachExcludingDependencies(
         dexType -> {
           DexProgramClass programClass =
               DexProgramClass.asProgramClassOrNull(application.definitionFor(dexType));
@@ -429,7 +428,7 @@
             }
           }
         });
-    mainDexClasses.addAll(newMainDexClasses);
+    newMainDexClasses.forEach(mainDexInfo::addSyntheticClass);
   }
 
   private void clearSynthesizedClassMapping(DexApplication application) {
@@ -443,7 +442,6 @@
       AppView<?> appView,
       Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
       Map<DexType, EquivalenceGroup<SyntheticProgramClassDefinition>> syntheticClassGroups,
-      MainDexClasses mainDexClasses,
       Builder lensBuilder,
       BiConsumer<DexProgramClass, SyntheticProgramClassReference> addFinalSyntheticClass,
       BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod) {
@@ -453,7 +451,8 @@
 
     // TODO(b/168584485): Remove this once class-mapping support is removed.
     Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
-    mainDexClasses.forEach(
+    MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
+    mainDexInfo.forEachExcludingDependencies(
         mainDexType -> {
           derivedMainDexTypes.add(mainDexType);
           DexProgramClass mainDexClass =
@@ -566,7 +565,7 @@
             addMainDexAndSynthesizedFromForMember(
                 member,
                 externalSyntheticClass,
-                mainDexClasses,
+                mainDexInfo,
                 derivedMainDexTypes,
                 appForLookup::programDefinitionFor);
           }
@@ -588,7 +587,7 @@
             addMainDexAndSynthesizedFromForMember(
                 member,
                 externalSyntheticClass,
-                mainDexClasses,
+                mainDexInfo,
                 derivedMainDexTypes,
                 appForLookup::programDefinitionFor);
           }
@@ -638,12 +637,12 @@
   private static void addMainDexAndSynthesizedFromForMember(
       SyntheticDefinition<?, ?, ?> member,
       DexProgramClass externalSyntheticClass,
-      MainDexClasses mainDexClasses,
+      MainDexInfo mainDexInfo,
       Set<DexType> derivedMainDexTypes,
       Function<DexType, DexProgramClass> definitions) {
     member
         .getContext()
-        .addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexClasses, derivedMainDexTypes);
+        .addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo, derivedMainDexTypes);
     // TODO(b/168584485): Remove this once class-mapping support is removed.
     DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType());
     if (from != null) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index c40dbee..14f9cf8 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -134,7 +134,7 @@
     CommittedItems commit =
         new CommittedItems(
             synthetics.nextSyntheticId, appView.appInfo().app(), committed, ImmutableList.of());
-    appView.setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexClasses()));
+    appView.setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexInfo()));
   }
 
   // Internal synthetic id creation helpers.
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 286c83d..2bac333 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -31,7 +31,7 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.tracereferences.TraceReferencesConsumer.AccessFlags;
 import com.android.tools.r8.tracereferences.TraceReferencesConsumer.ClassAccessFlags;
 import com.android.tools.r8.tracereferences.TraceReferencesConsumer.FieldAccessFlags;
@@ -236,7 +236,7 @@
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
             application,
             ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-            MainDexClasses.createEmptyMainDexClasses());
+            MainDexInfo.createEmptyMainDexClasses());
   }
 
   void run(TraceReferencesConsumer consumer) {
diff --git a/src/main/java/com/android/tools/r8/utils/LensUtils.java b/src/main/java/com/android/tools/r8/utils/LensUtils.java
index a039fb6..2c67ac3 100644
--- a/src/main/java/com/android/tools/r8/utils/LensUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/LensUtils.java
@@ -6,9 +6,11 @@
 
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.GraphLens;
 import com.google.common.collect.Sets;
 import java.util.Set;
+import java.util.function.Consumer;
 
 public class LensUtils {
 
@@ -20,4 +22,15 @@
     }
     return result;
   }
+
+  public static <T extends DexReference> void rewriteAndApplyIfNotPrimitiveType(
+      GraphLens graphLens, T reference, Consumer<T> rewrittenConsumer) {
+    T rewrittenReference = graphLens.rewriteReference(reference);
+    // Enum unboxing can change a class type to int which leads to errors going forward since
+    // the root set should not have primitive types.
+    if (rewrittenReference.isDexType() && rewrittenReference.asDexType().isPrimitiveType()) {
+      return;
+    }
+    rewrittenConsumer.accept(rewrittenReference);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 022f5d9..a278196 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -62,4 +62,11 @@
     }
     return out;
   }
+
+  public static <T> Set<T> unionIdentityHashSet(Set<T> one, Set<T> other) {
+    Set<T> union = Sets.newIdentityHashSet();
+    union.addAll(one);
+    union.addAll(other);
+    return union;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index fe0aa6c..3df21b1 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -46,7 +46,7 @@
 import com.android.tools.r8.references.TypeReference;
 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.MainDexInfo;
 import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardClassNameList;
@@ -710,7 +710,7 @@
     return AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
         readApplicationForDexOutput(app, new InternalOptions()),
         ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-        MainDexClasses.createEmptyMainDexClasses());
+        MainDexInfo.createEmptyMainDexClasses());
   }
 
   protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 1d7686c..55fcc74 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
@@ -113,7 +112,7 @@
 
     public String run() throws IOException {
       Timing timing = Timing.empty();
-      IRConverter converter = new IRConverter(appView, timing, null, MainDexTracingResult.NONE);
+      IRConverter converter = new IRConverter(appView, timing, null);
       converter.replaceCodeForTesting(method, code);
       AndroidApp app = writeDex();
       return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
new file mode 100644
index 0000000..5f21fee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+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.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+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.references.ClassReference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import org.junit.BeforeClass;
+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 MainDexListFromGenerateMainDexInliningSpuriousRootTest extends TestBase {
+
+  private static List<ClassReference> mainDexList;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    mainDexList =
+        testForMainDexListGenerator(getStaticTemp())
+            .addInnerClasses(MainDexListFromGenerateMainDexInliningSpuriousRootTest.class)
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " {",
+                "  public static void main(java.lang.String[]);",
+                "}")
+            .run()
+            .getMainDexList();
+  }
+
+  public MainDexListFromGenerateMainDexInliningSpuriousRootTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    // The generated main dex list should contain Main (which is a root) and A (which is a direct
+    // dependency of Main).
+    assertEquals(2, mainDexList.size());
+    assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
+    assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addInliningAnnotations()
+            .addKeepClassAndMembersRules(Main.class)
+            .addKeepMainRule(Main2.class)
+            .addMainDexListClassReferences(mainDexList)
+            .addMainDexRules(
+                "-keep class " + Main2.class.getTypeName() + " {",
+                "  public static void main(java.lang.String[]);",
+                "}")
+            .collectMainDexClasses()
+            .enableInliningAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .noMinification()
+            .compile();
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject mainClassSubject = inspector.clazz(Main.class);
+    assertThat(mainClassSubject, isPresent());
+    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooMethodSubject, isPresent());
+    ClassSubject main2ClassSubject = inspector.clazz(Main2.class);
+    assertThat(main2ClassSubject, isPresent());
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithName("bar");
+    assertThat(barMethodSubject, isPresent());
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithName("baz");
+    assertThat(bazMethodSubject, isPresent());
+    assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
+    assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
+    assertEquals(
+        ImmutableSet.of(
+            mainClassSubject.getFinalName(),
+            main2ClassSubject.getFinalName(),
+            aClassSubject.getFinalName()),
+        compileResult.getMainDexClasses());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("Main.main()");
+    }
+
+    static void foo() {
+      A.bar();
+    }
+  }
+
+  static class Main2 {
+
+    public static void main(String[] args) {
+      if (getFalse()) {
+        A.bar();
+      }
+    }
+
+    static boolean getFalse() {
+      return false;
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class A {
+    // Must not be inlined into Main.foo(), since that would cause B to become direct dependence of
+    // Main without ending up in the main dex.
+    static void bar() {
+      B.baz();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B {
+
+    @NeverInline
+    static void baz() {
+      System.out.println("B.baz");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
index a76d8cc..98ec426 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.maindexlist;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -21,6 +20,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
 import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -90,12 +90,10 @@
     assertThat(fooMethodSubject, isPresent());
 
     ClassSubject aClassSubject = inspector.clazz(A.class);
-    // TODO(b/178353726): Should be present, but was inlined.
-    assertThat(aClassSubject, isAbsent());
+    assertThat(aClassSubject, isPresent());
 
     MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithName("bar");
-    // TODO(b/178353726): Should be present, but was inlined.
-    assertThat(barMethodSubject, isAbsent());
+    assertThat(barMethodSubject, isPresent());
 
     ClassSubject bClassSubject = inspector.clazz(B.class);
     assertThat(bClassSubject, isPresent());
@@ -103,16 +101,13 @@
     MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithName("baz");
     assertThat(bazMethodSubject, isPresent());
 
-    // TODO(b/178353726): foo() should invoke bar() and bar() should invoke baz().
-    assertThat(fooMethodSubject, invokesMethod(bazMethodSubject));
+    assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
+    assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
 
-    // TODO(b/178353726): Main is the only class guaranteed to be in the main dex, but it has a
-    //  direct reference to B.
-    compileResult.inspectMainDexClasses(
-        mainDexClasses -> {
-          assertEquals(1, mainDexClasses.size());
-          assertEquals(mainClassSubject.getFinalName(), mainDexClasses.iterator().next());
-        });
+    // The main dex classes should be the same as the input main dex list.
+    assertEquals(
+        ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
+        compileResult.getMainDexClasses());
   }
 
   static class Main {
@@ -122,8 +117,8 @@
     }
 
     static void foo() {
-      // TODO(b/178353726): Should not allow inlining bar into foo(), since that adds B as a direct
-      //  dependence, and we don't include the direct dependencies of main dex list classes.
+      // Should not allow inlining bar into foo(), since that adds B as a direct
+      // dependence, and we don't include the direct dependencies of main dex list classes.
       A.bar();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
new file mode 100644
index 0000000..f5971f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+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.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+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.references.ClassReference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import org.junit.BeforeClass;
+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 MainDexListFromGenerateMainDexInliningWithTracingTest extends TestBase {
+
+  private static List<ClassReference> mainDexList;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    mainDexList =
+        testForMainDexListGenerator(getStaticTemp())
+            .addInnerClasses(MainDexListFromGenerateMainDexInliningWithTracingTest.class)
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " {",
+                "  public static void main(java.lang.String[]);",
+                "}")
+            .run()
+            .getMainDexList();
+  }
+
+  public MainDexListFromGenerateMainDexInliningWithTracingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    // The generated main dex list should contain Main (which is a root) and A (which is a direct
+    // dependency of Main).
+    assertEquals(2, mainDexList.size());
+    assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
+    assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
+
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addInliningAnnotations()
+            .addKeepClassAndMembersRules(Main.class)
+            .addMainDexListClassReferences(mainDexList)
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " {",
+                "  public static void foo(java.lang.String[]);",
+                "}")
+            .collectMainDexClasses()
+            .enableInliningAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
+            .noMinification()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject mainClassSubject = inspector.clazz(Main.class);
+    assertThat(mainClassSubject, isPresent());
+
+    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooMethodSubject, isPresent());
+
+    MethodSubject notCalledAtStartupMethodSubject =
+        mainClassSubject.uniqueMethodWithName("notCalledAtStartup");
+    assertThat(notCalledAtStartupMethodSubject, isPresent());
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithName("bar");
+    assertThat(barMethodSubject, isPresent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+
+    MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithName("baz");
+    assertThat(bazMethodSubject, isPresent());
+
+    assertThat(notCalledAtStartupMethodSubject, invokesMethod(barMethodSubject));
+    assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
+
+    // The main dex classes should be the same as the input main dex list.
+    assertEquals(
+        ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
+        compileResult.getMainDexClasses());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("Main.main()");
+    }
+
+    public static void notCalledAtStartup() {
+      // Should not allow inlining bar into notCalledAtStartup(), since that adds B as a direct
+      // dependence, and we don't include the direct dependencies of main dex list classes.
+      new A().bar();
+    }
+
+    // This method is traced for main dex when running with R8, to add A as a dependency.
+    static A foo(A a) {
+      if (a != null) {
+        System.out.println("Hello World");
+      }
+      return a;
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class A {
+
+    static void bar() {
+      B.baz();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B {
+
+    @NeverInline
+    static void baz() {
+      System.out.println("B.baz");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
index 867b1ec..c2c96ec 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.maindexlist;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -14,10 +13,12 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+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.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.TypeReference;
@@ -38,7 +39,6 @@
 public class MainDexListFromGenerateMainHorizontalMergingTest extends TestBase {
 
   private static List<ClassReference> mainDexList;
-
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
@@ -66,30 +66,45 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void testMainDexList() throws Exception {
     assertEquals(3, mainDexList.size());
     Set<String> mainDexReferences =
         mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
     assertTrue(mainDexReferences.contains(A.class.getTypeName()));
     assertTrue(mainDexReferences.contains(B.class.getTypeName()));
     assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
+    runTest(builder -> builder.addMainDexListClassReferences(mainDexList));
+  }
 
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .addInliningAnnotations()
-            .addKeepClassAndMembersRules(Main.class, Outside.class)
-            .addMainDexListClassReferences(mainDexList)
-            .collectMainDexClasses()
-            .enableInliningAnnotations()
-            .enableNeverClassInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
-            .addHorizontallyMergedClassesInspector(
-                horizontallyMergedClassesInspector -> {
-                  horizontallyMergedClassesInspector.assertMergedInto(B.class, A.class);
-                })
-            .compile();
+  @Test
+  public void testMainDexTracing() throws Exception {
+    runTest(
+        builder ->
+            builder.addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " { public static void foo(); }"));
+  }
 
+  private void runTest(ThrowableConsumer<R8FullTestBuilder> testBuilder) throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addInliningAnnotations()
+        .addKeepClassAndMembersRules(Main.class, Outside.class)
+        .collectMainDexClasses()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspector(
+            horizontallyMergedClassesInspector -> {
+              horizontallyMergedClassesInspector.assertClassesNotMerged(B.class, A.class);
+            })
+        .apply(testBuilder)
+        .compile()
+        .apply(this::inspectCompileResult)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputThatMatches(containsString(Outside.class.getTypeName()));
+  }
+
+  private void inspectCompileResult(R8TestCompileResult compileResult) throws Exception {
     CodeInspector inspector = compileResult.inspector();
     ClassSubject mainClassSubject = inspector.clazz(Main.class);
     assertThat(mainClassSubject, isPresent());
@@ -100,9 +115,8 @@
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
 
-    // TODO(b/178460068): Should be present, but was merged with A.
     ClassSubject bClassSubject = inspector.clazz(B.class);
-    assertThat(bClassSubject, isAbsent());
+    assertThat(bClassSubject, isPresent());
 
     MethodSubject fooASubject = aClassSubject.uniqueMethodWithName("foo");
     assertThat(fooASubject, isPresent());
@@ -112,19 +126,16 @@
     compileResult.inspectMainDexClasses(
         mainDexClasses -> {
           assertEquals(
-              ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
+              ImmutableSet.of(
+                  mainClassSubject.getFinalName(),
+                  aClassSubject.getFinalName(),
+                  bClassSubject.getFinalName()),
               mainDexClasses);
         });
-
-    compileResult
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputThatMatches(containsString(Outside.class.getTypeName()));
   }
 
   static class Main {
 
-    // public static B b;
-
     public static void main(String[] args) {
       B.print();
     }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
index e3d49f5..ec50d1e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.maindexlist;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -13,16 +12,17 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+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.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableSet;
 import java.util.List;
@@ -66,23 +66,34 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void testMainDexList() throws Exception {
     assertEquals(3, mainDexList.size());
     Set<String> mainDexReferences =
         mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
     assertTrue(mainDexReferences.contains(A.class.getTypeName()));
     assertTrue(mainDexReferences.contains(B.class.getTypeName()));
     assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
+    runTest(builder -> builder.addMainDexListClassReferences(mainDexList));
+  }
 
+  @Test
+  public void testMainTracing() throws Exception {
+    runTest(
+        builder ->
+            builder.addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " { public static void foo(); }"));
+  }
+
+  private void runTest(ThrowableConsumer<R8FullTestBuilder> testBuilder) throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addInnerClasses(getClass())
             .addInliningAnnotations()
             .addKeepClassAndMembersRules(Main.class, Outside.class)
-            .addMainDexListClassReferences(mainDexList)
             .collectMainDexClasses()
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
+            .apply(testBuilder)
             .setMinApi(parameters.getApiLevel())
             .compile();
 
@@ -94,25 +105,23 @@
     assertThat(fooMethodSubject, isPresent());
 
     ClassSubject aClassSubject = inspector.clazz(A.class);
-    // TODO(b/178460068): Should be present, but was merged with B.
-    assertThat(aClassSubject, isAbsent());
+    assertThat(aClassSubject, isPresent());
+
+    MethodSubject fooAMethodSubject = aClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooAMethodSubject, isPresent());
 
     ClassSubject bClassSubject = inspector.clazz(B.class);
     assertThat(bClassSubject, isPresent());
 
-    FieldSubject outsideFieldSubject = bClassSubject.uniqueFieldWithName("outsideField");
-    assertThat(outsideFieldSubject, isPresent());
+    assertThat(fooMethodSubject, invokesMethod(fooAMethodSubject));
 
-    MethodSubject fooBMethodSubject = bClassSubject.uniqueMethodWithName("foo");
-    assertThat(fooBMethodSubject, isPresent());
-
-    assertThat(fooMethodSubject, invokesMethod(fooBMethodSubject));
-
-    // TODO(b/178460068): B should not be in main dex.
     compileResult.inspectMainDexClasses(
         mainDexClasses -> {
           assertEquals(
-              ImmutableSet.of(mainClassSubject.getFinalName(), bClassSubject.getFinalName()),
+              ImmutableSet.of(
+                  mainClassSubject.getFinalName(),
+                  aClassSubject.getFinalName(),
+                  bClassSubject.getFinalName()),
               mainDexClasses);
         });
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
index 82f5e7d..09df9d4 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.maindexlist;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
@@ -55,10 +54,9 @@
     ClassSubject mainClassSubject = inspector.clazz(Main.class);
     assertThat(mainClassSubject, isPresent());
 
-    // A is absent due to inlining.
-    // TODO(b/178353726): Inlining should be prohibited.
+    // A is not allowed to be inlined and is therefore present.
     ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isAbsent());
+    assertThat(aClassSubject, isPresent());
 
     // B should be referenced from Main.main.
     ClassSubject bClassSubject = inspector.clazz(B.class);
@@ -67,6 +65,9 @@
     compileResult.inspectMainDexClasses(
         mainDexClasses -> {
           assertTrue(mainDexClasses.contains(mainClassSubject.getFinalName()));
+          // Since we passed a main-dex list the traced references A and B are not automagically
+          // included.
+          assertFalse(mainDexClasses.contains(aClassSubject.getFinalName()));
           assertFalse(mainDexClasses.contains(bClassSubject.getFinalName()));
         });
   }