Merge commit 'e6089b524bbfcc6a3f08fa0d84610c9f26a27bfc' into dev-release
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/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index da20df4..c5eac58 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -476,7 +476,6 @@
     assert !internal.enableEnumValueOptimization;
     assert !internal.outline.enabled;
     assert !internal.enableValuePropagation;
-    assert !internal.enableLambdaMerging;
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
 
     internal.desugarState = getDesugarState();
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 f91a003..2ebde33 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -16,9 +16,9 @@
 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.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.MainDexRootSet;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.Box;
@@ -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());
@@ -84,8 +72,9 @@
 
     SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
 
-    RootSet mainDexRootSet =
-        RootSet.builder(appView, subtypingInfo, options.mainDexKeepRules).build(executor);
+    MainDexRootSet mainDexRootSet =
+        MainDexRootSet.builder(appView, subtypingInfo, options.mainDexKeepRules).build(executor);
+    appView.setMainDexRootSet(mainDexRootSet);
 
     GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
     WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
@@ -95,26 +84,20 @@
     }
 
     Enqueuer enqueuer =
-        EnqueuerFactory.createForFinalMainDexTracing(
-            appView, executor, subtypingInfo, graphConsumer, MainDexTracingResult.NONE);
-    Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
-    // LiveTypes is the result.
-    MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
-    resultConsumer.accept(mainDexTracingResult);
-
+        EnqueuerFactory.createForGenerateMainDexList(
+            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,
@@ -124,6 +107,8 @@
         options,
         timing,
         executor);
+
+    return mainDexInfo;
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 8bf7cc6..b552bb2 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -174,7 +174,6 @@
     assert !internal.enableEnumValueOptimization;
     assert !internal.outline.enabled;
     assert !internal.enableValuePropagation;
-    assert !internal.enableLambdaMerging;
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
 
     assert internal.desugarState == DesugarState.ON;
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 782094c..80b5ec7 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.none());
   }
 
   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 9c93e0e..283e461 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -88,12 +88,12 @@
 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;
+import com.android.tools.r8.shaking.RootSetUtils.MainDexRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
@@ -284,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());
       }
 
@@ -326,7 +326,8 @@
                   .appInfo()
                   .rebuildWithClassHierarchy(
                       MissingClasses.builderForInitialMissingClasses()
-                          .addNewMissingClasses(new SubtypingInfo(appView).getMissingClasses())
+                          .legacyAddNewMissingClasses(
+                              new SubtypingInfo(appView).getMissingClasses())
                           .reportMissingClasses(appView)));
         }
 
@@ -421,23 +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.
-      RootSet mainDexRootSet = null;
-      MainDexTracingResult mainDexTracingResult = MainDexTracingResult.NONE;
-      if (!options.mainDexKeepRules.isEmpty()) {
-        assert appView.graphLens().isIdentityLens();
-        // Find classes which may have code executed before secondary dex files installation.
-        SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
-        mainDexRootSet =
-            RootSet.builder(appView, subtypingInfo, options.mainDexKeepRules)
-                .build(executorService);
-        // Live types is the tracing result.
-        Set<DexProgramClass> mainDexBaseClasses =
-            EnqueuerFactory.createForInitialMainDexTracing(appView, executorService, subtypingInfo)
-                .traceMainDex(mainDexRootSet, executorService, timing);
-        // Calculate the automatic main dex list according to legacy multidex constraints.
-        mainDexTracingResult = new MainDexListBuilder(mainDexBaseClasses, appView).run();
-        appView.appInfo().unsetObsolete();
-      }
+      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
@@ -479,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);
@@ -530,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
@@ -547,8 +528,6 @@
                     .addRemovedClasses(appView.horizontallyMergedClasses().getSources())
                     .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getTargets())
                     .build());
-
-            mainDexTracingResult = horizontalClassMergerResult.getMainDexTracingResult();
           }
           timing.end();
         } else {
@@ -556,6 +535,9 @@
         }
       }
 
+      // Clear traced methods roots to not hold on to the main dex live method set.
+      appView.appInfo().getMainDexInfo().clearTracedMethodRoots();
+
       // None of the optimizations above should lead to the creation of type lattice elements.
       assert appView.dexItemFactory().verifyNoCachedTypeElements();
 
@@ -571,7 +553,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));
@@ -601,55 +583,6 @@
         }
       }
 
-      if (!options.mainDexKeepRules.isEmpty()) {
-        // No need to build a new main dex root set
-        assert mainDexRootSet != null;
-        GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
-        WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
-        if (!mainDexRootSet.reasonAsked.isEmpty()) {
-          whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(mainDexKeptGraphConsumer);
-          mainDexKeptGraphConsumer = whyAreYouKeepingConsumer;
-        }
-
-        Enqueuer enqueuer =
-            EnqueuerFactory.createForFinalMainDexTracing(
-                appView,
-                executorService,
-                new SubtypingInfo(appView),
-                mainDexKeptGraphConsumer,
-                mainDexTracingResult);
-        // Find classes which may have code executed before secondary dex files installation.
-        // Live types is the tracing result.
-        Set<DexProgramClass> mainDexBaseClasses =
-            enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
-        // Calculate the automatic main dex list according to legacy multidex constraints.
-        mainDexTracingResult = new MainDexListBuilder(mainDexBaseClasses, appView).run();
-        final MainDexTracingResult finalMainDexClasses = mainDexTracingResult;
-
-        processWhyAreYouKeepingAndCheckDiscarded(
-            mainDexRootSet,
-            () -> {
-              ArrayList<DexProgramClass> classes = new ArrayList<>();
-              // TODO(b/131668850): This is not a deterministic order!
-              finalMainDexClasses
-                  .getClasses()
-                  .forEach(
-                      type -> {
-                        DexClass clazz = appView.definitionFor(type);
-                        assert clazz.isProgramClass();
-                        classes.add(clazz.asProgramClass());
-                      });
-              return classes;
-            },
-            whyAreYouKeepingConsumer,
-            appView,
-            enqueuer,
-            true,
-            options,
-            timing,
-            executorService);
-      }
-
       if (options.shouldRerunEnqueuer()) {
         timing.begin("Post optimization code stripping");
         try {
@@ -731,12 +664,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);
           }
@@ -749,7 +676,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
@@ -767,6 +694,8 @@
         }
       }
 
+      performFinalMainDexTracing(appView, executorService);
+
       // Remove unneeded visibility bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
       if (appView.appInfo().hasLiveness()) {
@@ -803,11 +732,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 {
@@ -913,6 +837,71 @@
     }
   }
 
+  private void performInitialMainDexTracing(
+      AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+      throws ExecutionException {
+    if (options.mainDexKeepRules.isEmpty()) {
+      return;
+    }
+    assert appView.graphLens().isIdentityLens();
+    // Find classes which may have code executed before secondary dex files installation.
+    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+    MainDexRootSet mainDexRootSet =
+        MainDexRootSet.builder(appView, subtypingInfo, options.mainDexKeepRules)
+            .build(executorService);
+    appView.setMainDexRootSet(mainDexRootSet);
+    appView.appInfo().unsetObsolete();
+    // Live types is the tracing result.
+    MainDexInfo mainDexInfo =
+        EnqueuerFactory.createForInitialMainDexTracing(appView, executorService, subtypingInfo)
+            .traceMainDex(executorService, timing);
+    appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
+  }
+
+  private void performFinalMainDexTracing(
+      AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+      throws ExecutionException {
+    if (options.mainDexKeepRules.isEmpty()) {
+      return;
+    }
+    // No need to build a new main dex root set
+    assert appView.getMainDexRootSet() != null;
+    GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
+    WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
+    if (!appView.getMainDexRootSet().reasonAsked.isEmpty()) {
+      whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(mainDexKeptGraphConsumer);
+      mainDexKeptGraphConsumer = whyAreYouKeepingConsumer;
+    }
+
+    Enqueuer enqueuer =
+        EnqueuerFactory.createForFinalMainDexTracing(
+            appView, executorService, new SubtypingInfo(appView), mainDexKeptGraphConsumer);
+    // Find classes which may have code executed before secondary dex files installation.
+    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!
+          mainDexInfo.forEach(
+              type -> {
+                DexClass clazz = appView.definitionFor(type);
+                assert clazz.isProgramClass();
+                classes.add(clazz.asProgramClass());
+              });
+          return classes;
+        },
+        whyAreYouKeepingConsumer,
+        appView,
+        enqueuer,
+        true,
+        options,
+        timing,
+        executorService);
+  }
+
   private static boolean verifyMovedMethodsHaveOriginalMethodPosition(
       AppView<?> appView, DirectMappedDexApplication application) {
     application
@@ -1042,12 +1031,8 @@
       if (forMainDex) {
         enqueuer =
             EnqueuerFactory.createForFinalMainDexTracing(
-                appView,
-                executorService,
-                subtypingInfo,
-                whyAreYouKeepingConsumer,
-                MainDexTracingResult.NONE);
-        enqueuer.traceMainDex(rootSet, executorService, timing);
+                appView, executorService, subtypingInfo, whyAreYouKeepingConsumer);
+        enqueuer.traceMainDex(executorService, timing);
       } else {
         enqueuer =
             EnqueuerFactory.createForWhyAreYouKeeping(
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 0fa55a7..8d07b18 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -834,10 +834,6 @@
     internal.desugarState = getDesugarState();
     assert internal.isShrinking() == getEnableTreeShaking();
     assert internal.isMinifying() == getEnableMinification();
-    // In current implementation we only enable lambda merger if the tree
-    // shaking is enabled. This is caused by the fact that we rely on tree
-    // shaking for removing the lambda classes which should be revised later.
-    internal.enableLambdaMerging = getEnableTreeShaking();
     assert !internal.ignoreMissingClasses;
     internal.ignoreMissingClasses =
         proguardConfiguration.isIgnoreWarnings()
@@ -871,14 +867,12 @@
       internal.enableClassStaticizer = false;
       internal.outline.enabled = false;
       internal.enableEnumUnboxing = false;
-      internal.enableLambdaMerging = false;
     }
     if (!internal.isShrinking()) {
       // If R8 is not shrinking, there is no point in running various optimizations since the
       // optimized classes will still remain in the program (the application size could increase).
       internal.enableEnumUnboxing = false;
       internal.horizontalClassMergerOptions().disable();
-      internal.enableLambdaMerging = false;
       internal.enableVerticalClassMerging = false;
     }
 
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..21de6ac 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.none().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/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index dfa8667..8ea7319 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -135,6 +135,7 @@
   public static final int ACC_ANNOTATION = 0x2000;
   public static final int ACC_ENUM = 0x4000;
   public static final int ACC_CONSTRUCTOR = 0x10000;
+  public static final int ACC_RECORD = 0x10000;
   public static final int ACC_DECLARED_SYNCHRONIZED = 0x20000;
 
   public static final String JAVA_LANG_OBJECT_NAME = "java/lang/Object";
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..de3d976 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.none());
   }
 
-  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 7d574dd..b56d682 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
-import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
@@ -32,8 +31,9 @@
 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;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
@@ -64,6 +64,7 @@
   private InitClassLens initClassLens;
   private ProguardCompatibilityActions proguardCompatibilityActions;
   private RootSet rootSet;
+  private MainDexRootSet mainDexRootSet = null;
   // This should perferably always be obtained via AppInfoWithLiveness.
   // Currently however the liveness may be downgraded thus loosing the computed keep info.
   private KeepInfoCollection keepInfo = null;
@@ -91,13 +92,11 @@
   private boolean allCodeProcessed = false;
   private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
   private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
-  private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private HorizontallyMergedClasses horizontallyMergedClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
   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.none());
   }
 
   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));
   }
@@ -460,6 +459,15 @@
     this.rootSet = rootSet;
   }
 
+  public void setMainDexRootSet(MainDexRootSet mainDexRootSet) {
+    assert mainDexRootSet != null : "Root set should never be recomputed";
+    this.mainDexRootSet = mainDexRootSet;
+  }
+
+  public MainDexRootSet getMainDexRootSet() {
+    return mainDexRootSet;
+  }
+
   public KeepInfoCollection getKeepInfo() {
     return keepInfo;
   }
@@ -483,9 +491,6 @@
     if (horizontallyMergedClasses != null) {
       collection.add(horizontallyMergedClasses);
     }
-    if (horizontallyMergedLambdaClasses != null) {
-      collection.add(horizontallyMergedLambdaClasses);
-    }
     if (verticallyMergedClasses != null) {
       collection.add(verticallyMergedClasses);
     }
@@ -493,23 +498,6 @@
   }
 
   /**
-   * Get the result of horizontal lambda class merging. Returns null if horizontal lambda class
-   * merging has not been run.
-   */
-  public HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses() {
-    return horizontallyMergedLambdaClasses;
-  }
-
-  public void setHorizontallyMergedLambdaClasses(
-      HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses) {
-    assert this.horizontallyMergedLambdaClasses == null;
-    this.horizontallyMergedLambdaClasses = horizontallyMergedLambdaClasses;
-    testing()
-        .horizontallyMergedLambdaClassesConsumer
-        .accept(dexItemFactory(), horizontallyMergedLambdaClasses);
-  }
-
-  /**
    * Get the result of horizontal class merging. Returns null if horizontal class merging has not
    * been run.
    */
@@ -619,6 +607,9 @@
       setProguardCompatibilityActions(
           getProguardCompatibilityActions().withoutPrunedItems(prunedItems));
     }
+    if (mainDexRootSet != null) {
+      setMainDexRootSet(mainDexRootSet.withoutPrunedItems(prunedItems));
+    }
   }
 
   @SuppressWarnings("unchecked")
@@ -688,6 +679,9 @@
             appView.setProguardCompatibilityActions(
                 appView.getProguardCompatibilityActions().rewrittenWithLens(lens));
           }
+          if (appView.getMainDexRootSet() != null) {
+            appView.setMainDexRootSet(appView.getMainDexRootSet().rewrittenWithLens(lens));
+          }
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 7998171..abeb404 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -22,9 +22,7 @@
   private static final int DEX_FLAGS
       = SHARED_FLAGS;
 
-  private static final int CF_FLAGS
-      = SHARED_FLAGS
-      | Constants.ACC_SUPER;
+  private static final int CF_FLAGS = SHARED_FLAGS | Constants.ACC_SUPER | Constants.ACC_RECORD;
 
   @Override
   protected List<String> getNames() {
@@ -35,6 +33,7 @@
         .add("annotation")
         .add("enum")
         .add("super")
+        .add("record")
         .build();
   }
 
@@ -47,6 +46,7 @@
         .add(this::isAnnotation)
         .add(this::isEnum)
         .add(this::isSuper)
+        .add(this::isRecord)
         .build();
   }
 
@@ -178,6 +178,14 @@
     set(Constants.ACC_ENUM);
   }
 
+  public boolean isRecord() {
+    return isSet(Constants.ACC_RECORD);
+  }
+
+  public void setRecord() {
+    set(Constants.ACC_RECORD);
+  }
+
   public boolean isSuper() {
     return isSet(Constants.ACC_SUPER);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathDefinition.java b/src/main/java/com/android/tools/r8/graph/ClasspathDefinition.java
new file mode 100644
index 0000000..89013fd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathDefinition.java
@@ -0,0 +1,23 @@
+// 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.graph;
+
+import java.util.function.Function;
+
+public interface ClasspathDefinition extends Definition {
+
+  @Override
+  default <T> T apply(
+      Function<ProgramDefinition, T> programFunction,
+      Function<ClasspathDefinition, T> classpathFunction,
+      Function<LibraryDefinition, T> libraryFunction) {
+    return classpathFunction.apply(this);
+  }
+
+  @Override
+  default ProgramDerivedContext asProgramDerivedContext(ProgramDerivedContext witness) {
+    return ClasspathOrLibraryContext.create(this, witness);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathField.java b/src/main/java/com/android/tools/r8/graph/ClasspathField.java
index b40e51c..ca7b15b 100644
--- a/src/main/java/com/android/tools/r8/graph/ClasspathField.java
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathField.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.graph;
 
-public class ClasspathField extends DexClassAndField {
+public class ClasspathField extends DexClassAndField
+    implements ClasspathMember<DexEncodedField, DexField> {
 
   public ClasspathField(DexClasspathClass holder, DexEncodedField field) {
     super(holder, field);
@@ -24,4 +25,11 @@
   public boolean isClasspathMember() {
     return true;
   }
+
+  @Override
+  public DexClasspathClass getHolder() {
+    DexClass holder = super.getHolder();
+    assert holder.isClasspathClass();
+    return holder.asClasspathClass();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathMember.java b/src/main/java/com/android/tools/r8/graph/ClasspathMember.java
new file mode 100644
index 0000000..8fad10f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathMember.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+public interface ClasspathMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+    extends ClasspathDefinition {
+
+  D getDefinition();
+
+  DexClasspathClass getHolder();
+
+  DexType getHolderType();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
index 0c58053..3f796e3 100644
--- a/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
@@ -6,7 +6,8 @@
 import com.android.tools.r8.logging.Log;
 
 /** Type representing a method definition on the classpath and its holder. */
-public final class ClasspathMethod extends DexClassAndMethod {
+public final class ClasspathMethod extends DexClassAndMethod
+    implements ClasspathMember<DexEncodedMethod, DexMethod> {
 
   public ClasspathMethod(DexClasspathClass holder, DexEncodedMethod method) {
     super(holder, method);
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathOrLibraryContext.java b/src/main/java/com/android/tools/r8/graph/ClasspathOrLibraryContext.java
new file mode 100644
index 0000000..b498837
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathOrLibraryContext.java
@@ -0,0 +1,36 @@
+// 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.graph;
+
+/**
+ * A classpath or library definition that is guaranteed to be derived from the tracing of a program
+ * context.
+ */
+public class ClasspathOrLibraryContext implements ProgramDerivedContext {
+
+  private final Definition context;
+  private final ProgramDerivedContext programDerivedContext;
+
+  private ClasspathOrLibraryContext(
+      Definition context, ProgramDerivedContext programDerivedContext) {
+    this.context = context;
+    this.programDerivedContext = programDerivedContext;
+  }
+
+  public static ClasspathOrLibraryContext create(
+      ClasspathDefinition context, ProgramDerivedContext programDerivedContext) {
+    return new ClasspathOrLibraryContext(context, programDerivedContext);
+  }
+
+  public static ClasspathOrLibraryContext create(
+      LibraryDefinition context, ProgramDerivedContext programDerivedContext) {
+    return new ClasspathOrLibraryContext(context, programDerivedContext);
+  }
+
+  @Override
+  public Definition getContext() {
+    return context;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/Definition.java b/src/main/java/com/android/tools/r8/graph/Definition.java
new file mode 100644
index 0000000..a7e4ed9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/Definition.java
@@ -0,0 +1,21 @@
+// 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.graph;
+
+import java.util.function.Function;
+
+public interface Definition {
+
+  <T> T apply(
+      Function<ProgramDefinition, T> programFunction,
+      Function<ClasspathDefinition, T> classpathFunction,
+      Function<LibraryDefinition, T> libraryFunction);
+
+  ProgramDerivedContext asProgramDerivedContext(ProgramDerivedContext witness);
+
+  DexType getContextType();
+
+  DexReference getReference();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index f1c0a2f..df80a80 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -33,7 +33,7 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-public abstract class DexClass extends DexDefinition {
+public abstract class DexClass extends DexDefinition implements Definition {
 
   public interface FieldSetter {
     void setField(int index, DexEncodedField field);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index cef86d0..505a7ec 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.graph;
 
-public class DexClassAndField extends DexClassAndMember<DexEncodedField, DexField> {
+public abstract class DexClassAndField extends DexClassAndMember<DexEncodedField, DexField> {
 
   DexClassAndField(DexClass holder, DexEncodedField field) {
     super(holder, field);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
index 83c4334..8459067 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 
-public abstract class DexClassAndMember<
-    D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+public abstract class DexClassAndMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+    implements Definition {
 
   private final DexClass holder;
   private final D definition;
@@ -23,6 +23,7 @@
 
   public abstract AccessFlags<?> getAccessFlags();
 
+  @Override
   public DexType getContextType() {
     return getHolderType();
   }
@@ -43,6 +44,7 @@
     return getReference().getName();
   }
 
+  @Override
   public R getReference() {
     return definition.getReference();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 075c1ba..d944c02 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.graph;
 
-public class DexClassAndMethod extends DexClassAndMember<DexEncodedMethod, DexMethod>
+public abstract class DexClassAndMethod extends DexClassAndMember<DexEncodedMethod, DexMethod>
     implements LookupTarget {
 
   DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 7fb6023..125155e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -22,7 +22,7 @@
 import java.util.function.Supplier;
 
 public class DexClasspathClass extends DexClass
-    implements Supplier<DexClasspathClass>, StructuralItem<DexClasspathClass> {
+    implements ClasspathDefinition, Supplier<DexClasspathClass>, StructuralItem<DexClasspathClass> {
 
   public DexClasspathClass(
       DexType type,
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index f933754..d15bfc7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ConsumerUtils;
@@ -355,6 +356,7 @@
     private FieldTypeSignature genericSignature;
     private DexValue staticValue;
     private FieldOptimizationInfo optimizationInfo;
+    private Consumer<DexEncodedField> buildConsumer = ConsumerUtils.emptyConsumer();
 
     Builder(DexEncodedField from) {
       // Copy all the mutable state of a DexEncodedField here.
@@ -370,15 +372,21 @@
               : from.optimizationInfo.mutableCopy();
     }
 
-    public Builder fixupOptimizationInfo(Consumer<MutableFieldOptimizationInfo> consumer) {
-      if (optimizationInfo.isMutableFieldOptimizationInfo()) {
-        consumer.accept(optimizationInfo.asMutableFieldOptimizationInfo());
-      }
+    public Builder apply(Consumer<Builder> consumer) {
+      consumer.accept(this);
       return this;
     }
 
-    public Builder apply(Consumer<Builder> consumer) {
-      consumer.accept(this);
+    public Builder setAbstractValue(
+        AbstractValue abstractValue, AppView<AppInfoWithLiveness> appView) {
+      return addBuildConsumer(
+          fixedUpField ->
+              OptimizationFeedbackSimple.getInstance()
+                  .recordFieldHasAbstractValue(fixedUpField, appView, abstractValue));
+    }
+
+    private Builder addBuildConsumer(Consumer<DexEncodedField> consumer) {
+      this.buildConsumer = this.buildConsumer.andThen(consumer);
       return this;
     }
 
@@ -393,6 +401,7 @@
       if (optimizationInfo.isMutableFieldOptimizationInfo()) {
         dexEncodedField.setOptimizationInfo(optimizationInfo.asMutableFieldOptimizationInfo());
       }
+      buildConsumer.accept(dexEncodedField);
       return dexEncodedField;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c736831..c7a6e70 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.NumericType;
@@ -55,6 +56,7 @@
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -72,6 +74,7 @@
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
@@ -1536,6 +1539,7 @@
     private KotlinMethodLevelInfo kotlinMemberInfo;
     private final CfVersion classFileVersion;
     private boolean d8R8Synthesized;
+    private Consumer<DexEncodedMethod> buildConsumer = ConsumerUtils.emptyConsumer();
 
     private Builder(DexEncodedMethod from) {
       this(from, from.d8R8Synthesized);
@@ -1567,10 +1571,19 @@
       }
     }
 
-    public Builder fixupOptimizationInfo(Consumer<UpdatableMethodOptimizationInfo> consumer) {
-      if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
-        consumer.accept(optimizationInfo.asUpdatableMethodOptimizationInfo());
-      }
+    public Builder setSimpleInliningConstraint(
+        DexProgramClass holder, SimpleInliningConstraint simpleInliningConstraint) {
+      return addBuildConsumer(
+          newMethod ->
+              OptimizationFeedbackSimple.getInstance()
+                  .setSimpleInliningConstraint(
+                      // The method has not yet been installed so we cannot use
+                      // asProgramMethod(appView).
+                      new ProgramMethod(holder, newMethod), simpleInliningConstraint));
+    }
+
+    private Builder addBuildConsumer(Consumer<DexEncodedMethod> consumer) {
+      this.buildConsumer = this.buildConsumer.andThen(consumer);
       return this;
     }
 
@@ -1699,6 +1712,7 @@
       if (!isLibraryMethodOverride.isUnknown()) {
         result.setLibraryMethodOverride(isLibraryMethodOverride);
       }
+      buildConsumer.accept(result);
       return result;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 30ac84b..18b0434 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -214,6 +213,7 @@
   public final DexString stringDescriptor = createString("Ljava/lang/String;");
   public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
+  public final DexString recordDescriptor = createString("Ljava/lang/Record;");
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
   public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -343,6 +343,7 @@
   public final DexType stringType = createStaticallyKnownType(stringDescriptor);
   public final DexType stringArrayType = createStaticallyKnownType(stringArrayDescriptor);
   public final DexType objectType = createStaticallyKnownType(objectDescriptor);
+  public final DexType recordType = createStaticallyKnownType(recordDescriptor);
   public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
   public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
   public final DexType enumType = createStaticallyKnownType(enumDescriptor);
@@ -424,6 +425,26 @@
       createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
   public final DexType kotlinMetadataType = createStaticallyKnownType(kotlinMetadataDescriptor);
 
+  public final DexType doubleSummaryStatisticsConversionsType =
+      createStaticallyKnownType("Ljava/util/DoubleSummaryStatisticsConversions;");
+  public final DexType intSummaryStatisticsConversionsType =
+      createStaticallyKnownType("Ljava/util/IntSummaryStatisticsConversions;");
+  public final DexType longSummaryStatisticsConversionsType =
+      createStaticallyKnownType("Ljava/util/LongSummaryStatisticsConversions;");
+  public final DexType optionalConversionsType =
+      createStaticallyKnownType("Ljava/util/OptionalConversions;");
+  public final DexType timeConversionsType =
+      createStaticallyKnownType("Ljava/time/TimeConversions;");
+
+  public Iterable<DexType> getConversionTypes() {
+    return ImmutableList.of(
+        doubleSummaryStatisticsConversionsType,
+        intSummaryStatisticsConversionsType,
+        longSummaryStatisticsConversionsType,
+        optionalConversionsType,
+        timeConversionsType);
+  }
+
   public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
   public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
   public final DexType javaNioByteOrderType = createStaticallyKnownType("Ljava/nio/ByteOrder;");
@@ -454,10 +475,6 @@
       createStaticallyKnownType("Landroid/util/Property;");
   public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
 
-  public final DexString nestConstructorDescriptor =
-      createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
-  public final DexType nestConstructorType = createStaticallyKnownType(nestConstructorDescriptor);
-
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
   public final StringBuildingMethods stringBufferMethods =
@@ -540,8 +557,10 @@
       createProto(voidType, throwableType, autoCloseableType);
 
   public final DexString deserializeLambdaMethodName = createString("$deserializeLambda$");
+  public final DexType serializedLambdaType =
+      createStaticallyKnownType("Ljava/lang/invoke/SerializedLambda;");
   public final DexProto deserializeLambdaMethodProto =
-      createProto(objectType, createStaticallyKnownType("Ljava/lang/invoke/SerializedLambda;"));
+      createProto(objectType, serializedLambdaType);
 
   // Dex system annotations.
   // See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index df36fb7..f34162c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -17,7 +17,8 @@
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
-public class DexLibraryClass extends DexClass implements Supplier<DexLibraryClass> {
+public class DexLibraryClass extends DexClass
+    implements LibraryDefinition, Supplier<DexLibraryClass> {
 
   public DexLibraryClass(
       DexType type,
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 618ff5b..0fdd6ad 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -161,6 +161,11 @@
   }
 
   @Override
+  public DexProgramClass getContext() {
+    return this;
+  }
+
+  @Override
   public StructuralMapping<DexProgramClass> getStructuralMapping() {
     return DexProgramClass::specify;
   }
@@ -801,7 +806,7 @@
           private DexProgramClass findNext() {
             while (iterator.hasNext()) {
               DexType next = iterator.next();
-              DexClass clazz = definitions.definitionFor(next);
+              DexClass clazz = definitions.contextIndependentDefinitionFor(next);
               if (clazz != null && clazz.isProgramClass()) {
                 return clazz.asProgramClass();
               }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 76d2ea4..f806886 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -4,11 +4,11 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -36,7 +36,9 @@
           "$-DC",
           "$$ServiceLoaderMethods",
           "com.android.tools.r8.GeneratedOutlineSupport",
-          "-$$Lambda$");
+          "-$$Nest$Constructor",
+          "-$$Lambda$",
+          "-$$LambdaGroup$");
 
   public final DexString descriptor;
   private String toStringCache = null;
@@ -46,6 +48,10 @@
     this.descriptor = descriptor;
   }
 
+  public ClassReference asClassReference() {
+    return Reference.classFromDescriptor(toDescriptorString());
+  }
+
   @Override
   public DexType self() {
     return this;
@@ -301,15 +307,7 @@
   }
 
   public boolean isLegacySynthesizedTypeAllowedDuplication() {
-    String name = toSourceString();
-    return isSynthesizedTypeThatCouldBeDuplicated(name) || oldSynthesizedName(name);
-  }
-
-  private static boolean isSynthesizedTypeThatCouldBeDuplicated(String name) {
-    // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
-    // newer releases can be used to merge previous builds.
-    return name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
-        || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME); // Global singleton.
+    return oldSynthesizedName(toSourceString());
   }
 
   private boolean oldSynthesizedName(String name) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 4483fc5..8b1ca9d 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -25,6 +25,10 @@
     return null;
   }
 
+  public DexClassAndField getResolutionPair() {
+    return null;
+  }
+
   public boolean isSuccessfulResolution() {
     return false;
   }
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 2f6c224..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);
@@ -413,14 +417,7 @@
   }
 
   public DexReference lookupReference(DexReference reference) {
-    if (reference.isDexType()) {
-      return lookupType(reference.asDexType());
-    } else if (reference.isDexMethod()) {
-      return lookupMethod(reference.asDexMethod());
-    } else {
-      assert reference.isDexField();
-      return lookupField(reference.asDexField());
-    }
+    return reference.apply(this::lookupType, this::lookupField, this::lookupMethod);
   }
 
   // The method lookupMethod() maps a pair INVOKE=(method signature, invoke type) to a new pair
@@ -532,14 +529,9 @@
 
   @SuppressWarnings("unchecked")
   public <T extends DexReference> T rewriteReference(T reference) {
-    if (reference.isDexField()) {
-      return (T) getRenamedFieldSignature(reference.asDexField());
-    }
-    if (reference.isDexMethod()) {
-      return (T) getRenamedMethodSignature(reference.asDexMethod());
-    }
-    assert reference.isDexType();
-    return (T) lookupType(reference.asDexType());
+    return (T)
+        reference.apply(
+            this::lookupType, this::getRenamedFieldSignature, this::getRenamedMethodSignature);
   }
 
   public Set<DexReference> rewriteReferences(Set<DexReference> references) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 40b6445..1f67c4d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -301,7 +301,8 @@
     @Override
     public RecordComponentVisitor visitRecordComponent(
         String name, String descriptor, String signature) {
-      throw new CompilationError("Records are not supported", origin);
+      // TODO(b/169645628): Support Records.
+      return super.visitRecordComponent(name, descriptor, signature);
     }
 
     @Override
@@ -326,6 +327,12 @@
       }
       this.deprecated = AsmUtils.isDeprecated(access);
       accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
+      if (accessFlags.isRecord()) {
+        // TODO(b/169645628): Support records in all compilation.
+        if (!application.options.canUseRecords()) {
+          throw new CompilationError("Records are not supported", origin);
+        }
+      }
       type = application.getTypeFromName(name);
       // Check if constraints from
       // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryDefinition.java b/src/main/java/com/android/tools/r8/graph/LibraryDefinition.java
new file mode 100644
index 0000000..a2fb6a8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LibraryDefinition.java
@@ -0,0 +1,23 @@
+// 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.graph;
+
+import java.util.function.Function;
+
+public interface LibraryDefinition extends Definition {
+
+  @Override
+  default <T> T apply(
+      Function<ProgramDefinition, T> programFunction,
+      Function<ClasspathDefinition, T> classpathFunction,
+      Function<LibraryDefinition, T> libraryFunction) {
+    return libraryFunction.apply(this);
+  }
+
+  @Override
+  default ProgramDerivedContext asProgramDerivedContext(ProgramDerivedContext witness) {
+    return ClasspathOrLibraryContext.create(this, witness);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryMember.java b/src/main/java/com/android/tools/r8/graph/LibraryMember.java
index 880da78..bf6bb03 100644
--- a/src/main/java/com/android/tools/r8/graph/LibraryMember.java
+++ b/src/main/java/com/android/tools/r8/graph/LibraryMember.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.graph;
 
-public interface LibraryMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+public interface LibraryMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+    extends LibraryDefinition {
 
   D getDefinition();
 
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index c55ad6f..862efbc 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -5,19 +5,29 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.origin.Origin;
+import java.util.function.Function;
 
-public interface ProgramDefinition {
+public interface ProgramDefinition extends Definition, ProgramDerivedContext {
+
+  @Override
+  default <T> T apply(
+      Function<ProgramDefinition, T> programFunction,
+      Function<ClasspathDefinition, T> classpathFunction,
+      Function<LibraryDefinition, T> libraryFunction) {
+    return programFunction.apply(this);
+  }
+
+  @Override
+  default ProgramDerivedContext asProgramDerivedContext(ProgramDerivedContext witness) {
+    return this;
+  }
 
   DexProgramClass getContextClass();
 
   AccessFlags<?> getAccessFlags();
 
-  DexType getContextType();
-
   DexDefinition getDefinition();
 
-  DexReference getReference();
-
   Origin getOrigin();
 
   default boolean isProgramClass() {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java b/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
new file mode 100644
index 0000000..f9ec920
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDerivedContext.java
@@ -0,0 +1,10 @@
+// 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.graph;
+
+public interface ProgramDerivedContext {
+
+  Definition getContext();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 866d230..42d2453 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -27,6 +27,11 @@
   }
 
   @Override
+  public ProgramField getContext() {
+    return this;
+  }
+
+  @Override
   public boolean isProgramField() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 3dc807c..a8817dc 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -63,6 +63,11 @@
   }
 
   @Override
+  public ProgramMethod getContext() {
+    return this;
+  }
+
+  @Override
   public boolean isProgramMember() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
deleted file mode 100644
index 3a9f618..0000000
--- a/src/main/java/com/android/tools/r8/graph/classmerging/HorizontallyMergedLambdaClasses.java
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph.classmerging;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.BiConsumer;
-
-public class HorizontallyMergedLambdaClasses implements MergedClasses {
-
-  private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses;
-
-  public HorizontallyMergedLambdaClasses(Map<DexType, LambdaGroup> lambdas) {
-    MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
-        new BidirectionalManyToOneHashMap<>();
-    lambdas.forEach((lambda, group) -> mergedClasses.put(lambda, group.getGroupClassType()));
-    this.mergedClasses = mergedClasses;
-  }
-
-  public static HorizontallyMergedLambdaClasses empty() {
-    return new HorizontallyMergedLambdaClasses(Collections.emptyMap());
-  }
-
-  @Override
-  public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
-    mergedClasses.forEachManyToOneMapping(consumer);
-  }
-
-  @Override
-  public boolean hasBeenMergedIntoDifferentType(DexType type) {
-    return mergedClasses.containsKey(type);
-  }
-
-  @Override
-  public boolean isMergeTarget(DexType type) {
-    return mergedClasses.containsValue(type);
-  }
-
-  @Override
-  public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
-    for (DexType source : mergedClasses.keySet()) {
-      assert appView.appInfo().wasPruned(source)
-          : "Expected horizontally merged lambda class `"
-              + source.toSourceString()
-              + "` to be absent";
-    }
-    return true;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index dd0486a..d97f12f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -23,14 +23,14 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
-import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinLambdas;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
 import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
 import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
 import com.android.tools.r8.horizontalclassmerging.policies.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;
@@ -40,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;
@@ -61,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);
 
@@ -80,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 =
@@ -92,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.
@@ -107,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(
@@ -127,9 +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),
@@ -138,20 +129,20 @@
         new NoEnums(appView),
         new CheckAbstractClasses(appView),
         new IgnoreSynthetics(appView),
-        new NoClassesOrMembersWithAnnotations(),
+        new NoClassesOrMembersWithAnnotations(appView),
         new NoInnerClasses(),
         new NoClassInitializerWithObservableSideEffects(),
         new NoNativeMethods(),
         new NoKeepRules(appView),
         new NoKotlinMetadata(),
-        new NoKotlinLambdas(appView),
         new NoServiceLoaders(appView),
         new NotVerticallyMergedIntoSubtype(appView),
         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/NoClassesOrMembersWithAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
index f9d2106..f0b2a6e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
@@ -4,12 +4,28 @@
 
 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.utils.InternalOptions.HorizontalClassMergerOptions;
 
 public class NoClassesOrMembersWithAnnotations extends SingleClassPolicy {
+
+  private final HorizontalClassMergerOptions options;
+
+  public NoClassesOrMembersWithAnnotations(AppView<AppInfoWithLiveness> appView) {
+    this.options = appView.options().horizontalClassMergerOptions();
+  }
+
   @Override
   public boolean canMerge(DexProgramClass program) {
     return !program.hasClassOrMemberAnnotations();
   }
+
+  @Override
+  public boolean shouldSkipPolicy() {
+    // TODO(b/179019716): Add support for merging in presence of annotations.
+    return options.skipNoClassesOrMembersWithAnnotationsPolicyForTesting;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java
index ddb2aef..cc9f61c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java
@@ -11,6 +11,7 @@
 
   @Override
   public boolean canMerge(DexProgramClass program) {
+    // TODO(b/179018501): allow merging classes with inner/outer classes.
     return program.getInnerClasses().isEmpty();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinLambdas.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinLambdas.java
deleted file mode 100644
index dee7a4a..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinLambdas.java
+++ /dev/null
@@ -1,33 +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.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public class NoKotlinLambdas extends SingleClassPolicy {
-  private final AppView<AppInfoWithLiveness> appView;
-
-  public NoKotlinLambdas(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
-  }
-
-  @Override
-  public boolean shouldSkipPolicy() {
-    return appView.options().horizontalClassMergerOptions().isKotlinLambdaMergingEnabled();
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    if (program.getKotlinInfo().isNoKotlinInformation()
-        || !program.getKotlinInfo().isSyntheticClass()) {
-      return true;
-    }
-
-    return !program.getKotlinInfo().asSyntheticClass().isLambda();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
index 92f2938..1acad7a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
@@ -19,12 +19,8 @@
   }
 
   private boolean verifyNoUnexpectedKotlinInfo(DexProgramClass clazz) {
-    if (clazz.getKotlinInfo().isNoKotlinInformation()) {
-      assert verifyNoUnexpectedKotlinMemberInfo(clazz);
-      return true;
-    }
-    assert clazz.getKotlinInfo().isSyntheticClass()
-        && clazz.getKotlinInfo().asSyntheticClass().isLambda();
+    assert clazz.getKotlinInfo().isNoKotlinInformation();
+    assert verifyNoUnexpectedKotlinMemberInfo(clazz);
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index fcafba0..4a789ba 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -20,6 +20,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Policy that enforces that methods are only merged if they have the same visibility and library
@@ -32,8 +33,10 @@
     private final MethodAccessFlags accessFlags;
     private final boolean isAssumeNoSideEffectsMethod;
     private final OptionalBool isLibraryMethodOverride;
+    private final boolean isMainDexRoot;
 
-    private MethodCharacteristics(boolean isAssumeNoSideEffectsMethod, DexEncodedMethod method) {
+    private MethodCharacteristics(
+        DexEncodedMethod method, boolean isAssumeNoSideEffectsMethod, boolean isMainDexRoot) {
       this.accessFlags =
           MethodAccessFlags.builder()
               .setPrivate(method.getAccessFlags().isPrivate())
@@ -44,17 +47,24 @@
               .build();
       this.isAssumeNoSideEffectsMethod = isAssumeNoSideEffectsMethod;
       this.isLibraryMethodOverride = method.isLibraryMethodOverride();
+      this.isMainDexRoot = isMainDexRoot;
     }
 
     static MethodCharacteristics create(
         AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
       return new MethodCharacteristics(
-          appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()), method);
+          method,
+          appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()),
+          appView.appInfo().getMainDexInfo().isTracedMethodRoot(method.getReference()));
     }
 
     @Override
     public int hashCode() {
-      return (accessFlags.hashCode() << 2) | isLibraryMethodOverride.ordinal();
+      return Objects.hash(
+          accessFlags,
+          isAssumeNoSideEffectsMethod,
+          isLibraryMethodOverride.ordinal(),
+          isMainDexRoot);
     }
 
     @Override
@@ -68,7 +78,8 @@
       MethodCharacteristics characteristics = (MethodCharacteristics) obj;
       return accessFlags.equals(characteristics.accessFlags)
           && isAssumeNoSideEffectsMethod == characteristics.isAssumeNoSideEffectsMethod
-          && isLibraryMethodOverride == characteristics.isLibraryMethodOverride;
+          && isLibraryMethodOverride == characteristics.isLibraryMethodOverride
+          && isMainDexRoot == characteristics.isMainDexRoot;
     }
   }
 
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/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 41ae033..24b17fb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -381,7 +381,7 @@
           // Map/required fields cannot be removed. Therefore, we mark such fields as both read and
           // written such that we cannot optimize any field reads or writes.
           enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
-          worklist.enqueueMarkReachableFieldAction(
+          worklist.enqueueMarkInstanceFieldAsReachableAction(
               valueStorage, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           valueStorageIsLive = true;
         } else {
@@ -446,7 +446,7 @@
           // Unconditionally register the hazzer and one-of proto fields as written from
           // dynamicMethod().
           if (enqueuer.registerReflectiveFieldWrite(newlyLiveField.getReference(), dynamicMethod)) {
-            worklist.enqueueMarkReachableFieldAction(
+            worklist.enqueueMarkInstanceFieldAsReachableAction(
                 newlyLiveField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           }
         }
@@ -566,7 +566,7 @@
     }
 
     if (enqueuer.registerReflectiveFieldWrite(oneOfField.getReference(), dynamicMethod)) {
-      worklist.enqueueMarkReachableFieldAction(
+      worklist.enqueueMarkInstanceFieldAsReachableAction(
           oneOfField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index 61ea47c..d8fb60c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -24,9 +25,10 @@
     this.executorService = executorService;
   }
 
-  public void awaitMethodProcessing() throws ExecutionException {
-    ThreadUtils.awaitFutures(futures);
-    futures.clear();
+  @Override
+  public boolean isProcessedConcurrently(ProgramMethod method) {
+    // In D8 all methods are considered independently compiled.
+    return true;
   }
 
   @Override
@@ -43,8 +45,13 @@
             executorService));
   }
 
-  public boolean verifyAllMethodsProcessed() {
-    assert futures.isEmpty();
-    return true;
+  @Override
+  public CallSiteInformation getCallSiteInformation() {
+    throw new Unreachable("Invalid attempt to obtain call-site information in D8");
+  }
+
+  public void awaitMethodProcessing() throws ExecutionException {
+    ThreadUtils.awaitFutures(futures);
+    futures.clear();
   }
 }
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 4131111..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
@@ -23,7 +23,6 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
@@ -82,7 +81,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
-import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -93,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;
@@ -123,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;
@@ -140,7 +136,6 @@
   private final TwrCloseResourceRewriter twrCloseResourceRewriter;
   private final BackportedMethodRewriter backportedMethodRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
-  private final LambdaMerger lambdaMerger;
   private final ClassInliner classInliner;
   private final ClassStaticizer classStaticizer;
   private final InternalOptions options;
@@ -185,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;
@@ -195,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 =
@@ -242,7 +235,6 @@
               : null;
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
-      this.lambdaMerger = null;
       this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
@@ -305,12 +297,9 @@
           options.enableTreeShakingOfLibraryMethodOverrides
               ? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
               : null;
-      this.lambdaMerger =
-          options.enableLambdaMerging ? new LambdaMerger(appViewWithLiveness) : null;
       this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
       this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
-      this.inliner =
-          new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
+      this.inliner = new Inliner(appViewWithLiveness, lensCodeRewriter);
       this.outliner = new Outliner(appViewWithLiveness);
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
@@ -322,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 =
@@ -346,7 +333,6 @@
       this.fieldAccessAnalysis = null;
       this.libraryMethodOverrideAnalysis = null;
       this.inliner = null;
-      this.lambdaMerger = null;
       this.outliner = null;
       this.memberValuePropagation = null;
       this.lensCodeRewriter = null;
@@ -372,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() {
@@ -398,12 +379,8 @@
     }
   }
 
-  private void finalizeNestBasedAccessDesugaring(Builder<?> builder) {
+  private void reportNestDesugarDependencies() {
     if (d8NestBasedAccessDesugaring != null) {
-      DexProgramClass nestConstructor = d8NestBasedAccessDesugaring.synthesizeNestConstructor();
-      if (nestConstructor != null) {
-        builder.addProgramClass(nestConstructor);
-      }
       d8NestBasedAccessDesugaring.reportDesugarDependencies();
     }
   }
@@ -489,10 +466,7 @@
 
     convertClasses(application, executor);
 
-    // Build a new application with jumbo string info,
-    Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
-
-    finalizeNestBasedAccessDesugaring(builder);
+    reportNestDesugarDependencies();
 
     // Synthesize lambda classes and commit to the app in full.
     synthesizeLambdaClasses(executor);
@@ -500,13 +474,14 @@
     if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
       appView.setAppInfo(
           new AppInfo(
-              appView.appInfo().getSyntheticItems().commit(builder.build()),
-              appView.appInfo().getMainDexClasses()));
+              appView.appInfo().getSyntheticItems().commit(application),
+              appView.appInfo().getMainDexInfo()));
       application = appView.appInfo().app();
-      builder = application.builder();
-      builder.setHighestSortingString(highestSortingString);
     }
 
+    // Build a new application with jumbo string info,
+    Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
+
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
@@ -520,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)
@@ -680,7 +655,6 @@
     DexApplication application = appView.appInfo().app();
 
     computeReachabilitySensitivity(application);
-    collectLambdaMergingCandidates(application);
     collectStaticizerCandidates(application);
     workaroundAbstractMethodOnNonAbstractClassVerificationBug(
         executorService, simpleOptimizationFeedback);
@@ -806,10 +780,6 @@
     synthesizeRetargetClass(builder, executorService);
     synthesizeEnumUnboxingUtilityMethods(executorService);
 
-    printPhase("Lambda merging finalization");
-    // TODO(b/127694949): Adapt to PostOptimization.
-    finalizeLambdaMerging(application, feedback, builder, executorService, initialGraphLensForIR);
-
     printPhase("Desugared library API Conversion finalization");
     generateDesugaredLibraryAPIWrappers(builder, executorService);
 
@@ -968,28 +938,6 @@
         method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
 
-  private void collectLambdaMergingCandidates(DexApplication application) {
-    if (lambdaMerger != null) {
-      lambdaMerger.collectGroupCandidates(application);
-    }
-  }
-
-  private void finalizeLambdaMerging(
-      DexApplication application,
-      OptimizationFeedback feedback,
-      Builder<?> builder,
-      ExecutorService executorService,
-      GraphLens appliedGraphLens)
-      throws ExecutionException {
-    if (lambdaMerger != null) {
-      lambdaMerger.applyLambdaClassMapping(
-          application, this, feedback, builder, executorService, appliedGraphLens);
-    } else {
-      appView.setHorizontallyMergedLambdaClasses(HorizontallyMergedLambdaClasses.empty());
-    }
-    assert appView.horizontallyMergedLambdaClasses() != null;
-  }
-
   private void generateDesugaredLibraryAPIWrappers(
       DexApplication.Builder<?> builder, ExecutorService executorService)
       throws ExecutionException {
@@ -1239,13 +1187,6 @@
             || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.method)
         : "Illegal reprocessing due to -neverreprocess rule: " + context.toSourceString();
 
-    if (lambdaMerger != null) {
-      timing.begin("Merge lambdas");
-      lambdaMerger.rewriteCode(code.context(), code, inliner, methodProcessor);
-      timing.end();
-      assert code.isConsistentSSA();
-    }
-
     if (typeChecker != null && !typeChecker.check(code)) {
       assert appView.enableWholeProgramOptimizations();
       assert options.testing.allowTypeErrors;
@@ -1335,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();
     }
 
@@ -1548,15 +1488,6 @@
 
     previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
 
-    if (lambdaMerger != null) {
-      timing.begin("Analyze lambda merging");
-      lambdaMerger.analyzeCode(code.context(), code);
-      timing.end();
-      assert code.isConsistentSSA();
-    }
-
-    previous = printMethod(code, "IR after lambda merger (SSA)", previous);
-
     // TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
     //  the list for primary processing only.
     if (options.outline.enabled && outliner != null && methodProcessor.isPrimaryMethodProcessor()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index ef9a31c..3be22fa 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -1,40 +1,21 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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.ir.conversion;
 
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 
 public abstract class MethodProcessor {
 
-  protected SortedProgramMethodSet wave;
-  protected SortedProgramMethodSet waveExtension = SortedProgramMethodSet.createConcurrent();
-
-  public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
-
   public boolean isPrimaryMethodProcessor() {
     return false;
   }
 
-  public CallSiteInformation getCallSiteInformation() {
-    return CallSiteInformation.empty();
-  }
+  public abstract boolean isProcessedConcurrently(ProgramMethod method);
 
-  public boolean isProcessedConcurrently(ProgramMethod method) {
-    return wave != null && wave.contains(method);
-  }
+  public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
 
-  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
-    waveExtension.add(method);
-  }
+  public abstract void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method);
 
-  protected void prepareForWaveExtensionProcessing() {
-    if (waveExtension.isEmpty()) {
-      wave = SortedProgramMethodSet.empty();
-    } else {
-      wave = waveExtension;
-      waveExtension = SortedProgramMethodSet.createConcurrent();
-    }
-  }
+  public abstract CallSiteInformation getCallSiteInformation();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
new file mode 100644
index 0000000..c50e8e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+
+public abstract class MethodProcessorWithWave extends MethodProcessor {
+
+  protected SortedProgramMethodSet wave;
+  protected SortedProgramMethodSet waveExtension = SortedProgramMethodSet.createConcurrent();
+
+  @Override
+  public CallSiteInformation getCallSiteInformation() {
+    return CallSiteInformation.empty();
+  }
+
+  @Override
+  public boolean isProcessedConcurrently(ProgramMethod method) {
+    return wave != null && wave.contains(method);
+  }
+
+  @Override
+  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+    waveExtension.add(method);
+  }
+
+  protected void prepareForWaveExtensionProcessing() {
+    if (waveExtension.isEmpty()) {
+      wave = SortedProgramMethodSet.empty();
+    } else {
+      wave = waveExtension;
+      waveExtension = SortedProgramMethodSet.createConcurrent();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index a8c2e57..be427aa 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -16,7 +16,7 @@
  * A {@link MethodProcessor} that doesn't persist; rather just processes the given methods one-time,
  * along with a default abstraction of concurrent processing.
  */
-public class OneTimeMethodProcessor extends MethodProcessor {
+public class OneTimeMethodProcessor extends MethodProcessorWithWave {
 
   private final MethodProcessingId.Factory methodProcessingIdFactory;
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 649edfd..ba7fa7f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -33,7 +33,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-public class PostMethodProcessor extends MethodProcessor {
+public class PostMethodProcessor extends MethodProcessorWithWave {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Collection<CodeOptimization> defaultCodeOptimizations;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index cb288b0..065d00b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -29,7 +29,7 @@
  * A {@link MethodProcessor} that processes methods in the whole program in a bottom-up manner,
  * i.e., from leaves to roots.
  */
-class PrimaryMethodProcessor extends MethodProcessor {
+class PrimaryMethodProcessor extends MethodProcessorWithWave {
 
   interface WaveStartAction {
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 860af39..f2f255a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -64,7 +64,7 @@
 public class DesugaredLibraryAPIConverter {
 
   static final String VIVIFIED_PREFIX = "$-vivified-$.";
-  private static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
+  public static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index a6606ed..6dbd525 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -701,7 +701,7 @@
     return dispatchTypeFor(method, "dispatchHolder");
   }
 
-  private static String getRetargetPackageAndClassPrefixDescriptor(
+  public static String getRetargetPackageAndClassPrefixDescriptor(
       DesugaredLibraryConfiguration config) {
     return "L"
         + config.getSynthesizedLibraryClassesPackagePrefix()
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 1b17172b5..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
@@ -14,6 +14,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
 
@@ -72,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;
@@ -97,6 +97,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 //
 // Default and static interface method desugaring rewriter (note that lambda
@@ -147,6 +148,7 @@
   // Caches default interface method info for already processed interfaces.
   private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
 
+  private final Predicate<DexType> shouldIgnoreFromReportsPredicate;
   /**
    * Defines a minor variation in desugaring.
    */
@@ -168,6 +170,7 @@
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
     this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
+    this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
     initializeEmulatedInterfaceVariables();
   }
 
@@ -712,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) {
@@ -724,8 +726,7 @@
                 theProgramInterface, emulatedInterfacesHierarchy);
         if (synthesizedClass != null) {
           builder.addSynthesizedClass(synthesizedClass);
-          appInfo.addSynthesizedClass(
-              synthesizedClass, mainDexClasses.contains(theProgramInterface));
+          appInfo.addSynthesizedClass(synthesizedClass, theProgramInterface);
         }
       }
     }
@@ -954,11 +955,6 @@
     return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
   }
 
-  public static boolean isTypeWrapper(DexType type) {
-    String name = type.toBinaryName();
-    return name.endsWith(TYPE_WRAPPER_SUFFIX) || name.endsWith(VIVIFIED_TYPE_WRAPPER_SUFFIX);
-  }
-
   // Gets the interface class for a companion class `type`.
   private DexType getInterfaceClassType(DexType type) {
     return getInterfaceClassType(type, factory);
@@ -1160,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 =
@@ -1175,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()) {
@@ -1291,13 +1283,34 @@
     return true;
   }
 
+  private Predicate<DexType> getShouldIgnoreFromReportsPredicate(AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    InternalOptions options = appView.options();
+    DexString retargetPackageAndClassPrefixDescriptor =
+        dexItemFactory.createString(
+            getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
+    DexString typeWrapperClassNameDescriptorSuffix =
+        dexItemFactory.createString(TYPE_WRAPPER_SUFFIX + ';');
+    DexString vivifiedTypeWrapperClassNameDescriptorSuffix =
+        dexItemFactory.createString(VIVIFIED_TYPE_WRAPPER_SUFFIX + ';');
+    DexString companionClassNameDescriptorSuffix =
+        dexItemFactory.createString(COMPANION_CLASS_NAME_SUFFIX + ";");
+
+    return type -> {
+      DexString descriptor = type.getDescriptor();
+      return appView.rewritePrefix.hasRewrittenType(type, appView)
+          || descriptor.endsWith(typeWrapperClassNameDescriptorSuffix)
+          || descriptor.endsWith(vivifiedTypeWrapperClassNameDescriptorSuffix)
+          || descriptor.endsWith(companionClassNameDescriptorSuffix)
+          || emulatedInterfaces.containsValue(type)
+          || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(type)
+          || appView.getDontWarnConfiguration().matches(type)
+          || descriptor.startsWith(retargetPackageAndClassPrefixDescriptor);
+    };
+  }
+
   private boolean shouldIgnoreFromReports(DexType missing) {
-    return appView.rewritePrefix.hasRewrittenType(missing, appView)
-        || isTypeWrapper(missing)
-        || isCompanionClassType(missing)
-        || emulatedInterfaces.containsValue(missing)
-        || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing)
-        || appView.getDontWarnConfiguration().matches(missing);
+    return shouldIgnoreFromReportsPredicate.test(missing);
   }
 
   void warnMissingInterface(DexClass classToDesugar, DexClass implementing, DexType missing) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 165cb52..c8c6a54 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -60,8 +60,6 @@
  */
 public class LambdaRewriter {
 
-  // Public for testing.
-  public static final String LAMBDA_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
   public static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index cbaa05a..c516ce8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -10,18 +10,14 @@
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMember;
@@ -30,21 +26,20 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.LibraryMember;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import org.objectweb.asm.Opcodes;
 
@@ -63,14 +58,10 @@
   private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
   private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
       NEST_ACCESS_NAME_PREFIX + "sfput";
-  public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
 
   protected final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
-
-  // Common single empty class for nest based private constructors
-  private DexProgramClass nestConstructor;
-  private boolean nestConstructorUsed;
+  private final Map<DexType, DexType> syntheticNestConstructorTypes = new ConcurrentHashMap<>();
 
   public NestBasedAccessDesugaring(AppView<?> appView) {
     this.appView = appView;
@@ -234,40 +225,6 @@
     throw appView.options().errorMissingNestMember(nest);
   }
 
-  private DexProgramClass createNestAccessConstructor() {
-    // TODO(b/176900254): ensure hygienic synthetic class.
-    return new DexProgramClass(
-        dexItemFactory.nestConstructorType,
-        null,
-        new SynthesizedOrigin("Nest based access desugaring", getClass()),
-        // Make the synthesized class public since shared in the whole program.
-        ClassAccessFlags.fromDexAccessFlags(
-            Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
-        dexItemFactory.objectType,
-        DexTypeList.empty(),
-        dexItemFactory.createString("nest"),
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        ClassSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        dexItemFactory.getSkipNameValidationForTesting(),
-        DexProgramClass::checksumFromType);
-  }
-
-  public DexProgramClass synthesizeNestConstructor() {
-    if (nestConstructorUsed && nestConstructor == null) {
-      nestConstructor = createNestAccessConstructor();
-      return nestConstructor;
-    }
-    return null;
-  }
-
   DexMethod ensureFieldAccessBridge(
       DexClassAndField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
     if (field.isProgramField()) {
@@ -360,9 +317,31 @@
 
   private DexMethod getMethodBridgeReference(DexClassAndMethod method) {
     if (method.getDefinition().isInstanceInitializer()) {
-      DexProto newProto =
-          dexItemFactory.appendTypeToProto(method.getProto(), dexItemFactory.nestConstructorType);
-      nestConstructorUsed = true;
+      DexType nestConstructorType =
+          syntheticNestConstructorTypes.computeIfAbsent(
+              method.getHolderType(),
+              holder -> {
+                if (method.isProgramMethod()) {
+                  return appView
+                      .getSyntheticItems()
+                      .createFixedClass(
+                          SyntheticKind.INIT_TYPE_ARGUMENT,
+                          method.asProgramMethod().getHolder(),
+                          dexItemFactory,
+                          builder -> {})
+                      .getType();
+                } else {
+                  assert method.isClasspathMethod();
+                  return appView
+                      .getSyntheticItems()
+                      .createFixedClasspathClass(
+                          SyntheticKind.INIT_TYPE_ARGUMENT,
+                          method.asClasspathMethod().getHolder(),
+                          dexItemFactory)
+                      .getType();
+                }
+              });
+      DexProto newProto = dexItemFactory.appendTypeToProto(method.getProto(), nestConstructorType);
       return method.getReference().withProto(newProto, dexItemFactory);
     }
     DexProto proto =
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 7eca624..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
@@ -57,10 +57,9 @@
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
-import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 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,9 +83,8 @@
 
   protected final AppView<AppInfoWithLiveness> appView;
   private final Set<DexMethod> extraNeverInlineMethods;
-  private final LambdaMerger lambdaMerger;
   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;
@@ -99,8 +97,6 @@
 
   public Inliner(
       AppView<AppInfoWithLiveness> appView,
-      MainDexTracingResult mainDexClasses,
-      LambdaMerger lambdaMerger,
       LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
@@ -108,9 +104,8 @@
         appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
             ? ImmutableSet.of()
             : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
-    this.lambdaMerger = lambdaMerger;
     this.lensCodeRewriter = lensCodeRewriter;
-    this.mainDexClasses = mainDexClasses;
+    this.mainDexInfo = appView.appInfo().getMainDexInfo();
     availableApiExceptions =
         appView.options().canHaveDalvikCatchHandlerVerificationBug()
             ? new AvailableApiExceptions(appView.options())
@@ -596,7 +591,6 @@
         InvokeMethod invoke,
         ProgramMethod context,
         InliningIRProvider inliningIRProvider,
-        LambdaMerger lambdaMerger,
         LensCodeRewriter lensCodeRewriter) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       InternalOptions options = appView.options();
@@ -743,9 +737,6 @@
         assert lensCodeRewriter != null;
         lensCodeRewriter.rewrite(code, target);
       }
-      if (lambdaMerger != null) {
-        lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
-      }
       if (options.testing.inlineeIrModifier != null) {
         options.testing.inlineeIrModifier.accept(code);
       }
@@ -1040,7 +1031,7 @@
 
           InlineeWithReason inlinee =
               action.buildInliningIR(
-                  appView, invoke, context, inliningIRProvider, lambdaMerger, lensCodeRewriter);
+                  appView, invoke, context, inliningIRProvider, lensCodeRewriter);
           if (strategy.willExceedBudget(
               code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
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/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 19a9da1..371b79a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -919,6 +919,12 @@
       return false;
     }
 
+    // We cannot guarantee the invoke returns the receiver or another instance and since the
+    // return value is used we have to bail out.
+    if (eligibility.returnsReceiver.isUnknown()) {
+      return false;
+    }
+
     // Add the out-value as a definite-alias if the invoke instruction is guaranteed to return the
     // receiver. Otherwise, the out-value may be an alias of the receiver, and it is added to the
     // may-alias set.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index bcd8458..17d0cc7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -16,6 +16,8 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.Sets;
@@ -31,14 +33,14 @@
 
   private final Map<DexType, List<DexEncodedMethod>> unboxedEnumsMethods = new IdentityHashMap<>();
   private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.enumUnboxingLensBuilder();
-  private final AppView<?> appView;
+  private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final Set<DexType> enumsToUnbox;
   private final UnboxedEnumMemberRelocator relocator;
   private final EnumUnboxingRewriter enumUnboxerRewriter;
 
   EnumUnboxingTreeFixer(
-      AppView<?> appView,
+      AppView<AppInfoWithLiveness> appView,
       Set<DexType> enumsToUnbox,
       UnboxedEnumMemberRelocator relocator,
       EnumUnboxingRewriter enumUnboxerRewriter) {
@@ -72,7 +74,9 @@
                 });
         clazz.getMethodCollection().removeMethods(methodsToRemove);
       } else {
-        clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
+        clazz
+            .getMethodCollection()
+            .replaceMethods(method -> this.fixupEncodedMethod(clazz, method));
         fixupFields(clazz.staticFields(), clazz::setStaticField);
         fixupFields(clazz.instanceFields(), clazz::setInstanceField);
       }
@@ -119,7 +123,7 @@
         newMethod, builder -> builder.setCompilationState(encodedMethod.getCompilationState()));
   }
 
-  private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
+  private DexEncodedMethod fixupEncodedMethod(DexProgramClass holder, DexEncodedMethod method) {
     DexProto oldProto = method.getProto();
     DexProto newProto = fixupProto(oldProto);
     if (newProto == method.getProto()) {
@@ -143,21 +147,24 @@
                 .setCompilationState(method.getCompilationState())
                 .setIsLibraryMethodOverrideIf(
                     method.isNonPrivateVirtualMethod(), OptionalBool.FALSE)
-                .fixupOptimizationInfo(
-                    optimizationInfo -> {
-                      IntList unboxedArgumentIndices = new IntArrayList();
-                      int offset = BooleanUtils.intValue(method.isInstance());
-                      for (int i = 0; i < method.getReference().getArity(); i++) {
-                        if (oldProto.getParameter(i).isReferenceType()
-                            && newProto.getParameter(i).isPrimitiveType()) {
-                          unboxedArgumentIndices.add(i + offset);
-                        }
-                      }
-                      optimizationInfo.setSimpleInliningConstraint(
-                          optimizationInfo
-                              .getSimpleInliningConstraint()
-                              .rewrittenWithUnboxedArguments(unboxedArgumentIndices));
-                    }));
+                .setSimpleInliningConstraint(
+                    holder, getRewrittenSimpleInliningConstraint(method, oldProto, newProto)));
+  }
+
+  private SimpleInliningConstraint getRewrittenSimpleInliningConstraint(
+      DexEncodedMethod method, DexProto oldProto, DexProto newProto) {
+    IntList unboxedArgumentIndices = new IntArrayList();
+    int offset = BooleanUtils.intValue(method.isInstance());
+    for (int i = 0; i < method.getReference().getArity(); i++) {
+      if (oldProto.getParameter(i).isReferenceType()
+          && newProto.getParameter(i).isPrimitiveType()) {
+        unboxedArgumentIndices.add(i + offset);
+      }
+    }
+    return method
+        .getOptimizationInfo()
+        .getSimpleInliningConstraint()
+        .rewrittenWithUnboxedArguments(unboxedArgumentIndices);
   }
 
   private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
@@ -197,11 +204,8 @@
             encodedField.toTypeSubstitutedField(
                 newField,
                 builder ->
-                    builder.fixupOptimizationInfo(
-                        mutableFieldOptimizationInfo -> {
-                          mutableFieldOptimizationInfo.setAbstractValue(
-                              encodedField.getOptimizationInfo().getAbstractValue());
-                        }));
+                    builder.setAbstractValue(
+                        encodedField.getOptimizationInfo().getAbstractValue(), appView));
         setter.setField(i, newEncodedField);
         if (encodedField.isStatic() && encodedField.hasExplicitStaticValue()) {
           assert encodedField.getStaticValue() == DexValue.DexValueNull.NULL;
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/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 3bf8d88..41ee658 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -65,7 +65,7 @@
     return abstractValue;
   }
 
-  public void setAbstractValue(AbstractValue abstractValue) {
+  void setAbstractValue(AbstractValue abstractValue) {
     this.abstractValue = abstractValue;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index e62b2c1..945d84a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -66,7 +66,9 @@
   @Override
   public void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
-    // Ignored.
+    if (appView.appInfo().mayPropagateValueFor(field.field)) {
+      field.getMutableOptimizationInfo().setAbstractValue(abstractValue);
+    }
   }
 
   // METHOD OPTIMIZATION INFO.
@@ -198,7 +200,7 @@
   @Override
   public void setSimpleInliningConstraint(
       ProgramMethod method, SimpleInliningConstraint constraint) {
-    // Ignored.
+    method.getDefinition().getMutableOptimizationInfo().setSimpleInliningConstraint(constraint);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 03e83b8..a179561 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -383,7 +383,7 @@
     setFlag(REACHABILITY_SENSITIVE_FLAG, reachabilitySensitive);
   }
 
-  public void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
+  void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
     this.simpleInliningConstraint = constraint;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CaptureSignature.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CaptureSignature.java
deleted file mode 100644
index 5a39e71..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CaptureSignature.java
+++ /dev/null
@@ -1,160 +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.ir.optimize.lambda;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
-import it.unimi.dsi.fastutil.ints.IntLists;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Comparator;
-import java.util.List;
-import java.util.function.IntFunction;
-
-// While mapping fields representing lambda captures we rearrange fields to make sure
-// lambdas having different order of the fields still can be merged. We also store
-// all captures of reference types in fields of java.lang.Objects class.
-//
-// This allows us to use same lambda groups class for these two different lambdas:
-//
-//      Lambda Group Class         Lambda$1               Lambda$2
-//          Object $c0        int      foo -> $c1     char foo -> $c2
-//          int    $c1        String[] bar -> $c0     int  bar -> $c1
-//          char   $c2        char     baz -> $c2     List baz -> $c0
-//
-// Capture signature is represented by a string with sorted shorties of field
-// types, such as "CIL" for both Lambda$1 and Lambda$2 in the above example.
-//
-public final class CaptureSignature {
-  private static final IntList EMPTY_LIST = IntLists.EMPTY_LIST;
-  private static final IntList SINGLE_LIST = IntLists.singleton(0);
-
-  private CaptureSignature() {
-  }
-
-  // Returns an array representing mapping of fields of the normalized capture
-  // into fields of original capture such that:
-  //
-  //   mapping = getReverseCaptureMapping(...)
-  //   <original-capture-index> = mapping[<normalized-capture-index>]
-  public static IntList getReverseCaptureMapping(DexType[] types) {
-    if (types.length == 0) {
-      return EMPTY_LIST;
-    }
-
-    if (types.length == 1) {
-      return SINGLE_LIST;
-    }
-
-    IntList result = new IntArrayList(types.length);
-    for (int i = 0; i < types.length; i++) {
-      result.add(i);
-    }
-    // Sort the indices by shorties (sorting is stable).
-    result.sort(Comparator.comparingInt(i -> types[i].toShorty()));
-    assert verifyMapping(result);
-    return result;
-  }
-
-  // Given a capture signature and an index returns the type of the field.
-  public static DexType fieldType(DexItemFactory factory, String capture, int index) {
-    switch (capture.charAt(index)) {
-      case 'L':
-        return factory.objectType;
-      case 'Z':
-        return factory.booleanType;
-      case 'B':
-        return factory.byteType;
-      case 'S':
-        return factory.shortType;
-      case 'C':
-        return factory.charType;
-      case 'I':
-        return factory.intType;
-      case 'F':
-        return factory.floatType;
-      case 'J':
-        return factory.longType;
-      case 'D':
-        return factory.doubleType;
-      default:
-        throw new Unreachable("Invalid capture character: " + capture.charAt(index));
-    }
-  }
-
-  private static String getCaptureSignature(int size, IntFunction<DexType> type) {
-    if (size == 0) {
-      return "";
-    }
-    if (size == 1) {
-      return Character.toString(type.apply(0).toShorty());
-    }
-
-    char[] chars = new char[size];
-    for (int i = 0; i < size; i++) {
-      chars[i] = type.apply(i).toShorty();
-    }
-    Arrays.sort(chars);
-    return new String(chars);
-  }
-
-  // Compute capture signature based on lambda class capture fields.
-  public static String getCaptureSignature(List<DexEncodedField> fields) {
-    return getCaptureSignature(fields.size(), i -> fields.get(i).field.type);
-  }
-
-  // Compute capture signature based on type list.
-  public static String getCaptureSignature(DexTypeList types) {
-    return getCaptureSignature(types.values.length, i -> types.values[i]);
-  }
-
-  // Having a list of fields of lambda captured values, maps one of them into
-  // an index of the appropriate field in normalized capture signature.
-  public static int mapFieldIntoCaptureIndex(
-      String capture, List<DexEncodedField> lambdaFields, DexField fieldToMap) {
-    char fieldKind = fieldToMap.type.toShorty();
-    int numberOfSameCaptureKind = 0;
-    int result = -1;
-
-    for (DexEncodedField encodedField : lambdaFields) {
-      if (encodedField.field == fieldToMap) {
-        result = numberOfSameCaptureKind;
-        break;
-      }
-      if (encodedField.field.type.toShorty() == fieldKind) {
-        numberOfSameCaptureKind++;
-      }
-    }
-
-    assert result >= 0 : "Field was not found in the lambda class.";
-
-    for (int index = 0; index < capture.length(); index++) {
-      if (capture.charAt(index) == fieldKind) {
-        if (result == 0) {
-          return index;
-        }
-        result--;
-      }
-    }
-
-    throw new Unreachable("Were not able to map lambda field into capture index");
-  }
-
-  private static boolean verifyMapping(IntList mapping) {
-    BitSet bits = new BitSet();
-    for (Integer i : mapping) {
-      assert i >= 0 && i < mapping.size();
-      assert !bits.get(i);
-      bits.set(i);
-    }
-    return true;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
deleted file mode 100644
index 1da512d..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ /dev/null
@@ -1,430 +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.ir.optimize.lambda;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.Argument;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstMethodHandle;
-import com.android.tools.r8.ir.code.ConstMethodType;
-import com.android.tools.r8.ir.code.DefaultInstructionVisitor;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InitClass;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.NewArrayEmpty;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
-import com.android.tools.r8.ir.optimize.lambda.LambdaMerger.ApplyStrategy;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.ListIterator;
-import java.util.function.Function;
-
-// Performs processing of the method code (all methods) needed by lambda rewriter.
-//
-// Functionality can be modified by strategy (specific to lambda group) and lambda
-// type visitor.
-//
-// In general it is used to
-//   (a) track the code for illegal lambda type usages inside the code, and
-//   (b) patching valid lambda type references to point to lambda group classes instead.
-//
-// This class is also used inside particular strategies as a context of the instruction
-// being checked or patched, it provides access to code, block and instruction iterators.
-public abstract class CodeProcessor extends DefaultInstructionVisitor<Void> {
-  // Strategy (specific to lambda group) for detecting valid references to the
-  // lambda classes (of this group) and patching them with group class references.
-  public interface Strategy {
-    LambdaGroup group();
-
-    boolean isValidStaticFieldWrite(CodeProcessor context, DexField field);
-
-    boolean isValidStaticFieldRead(CodeProcessor context, DexField field);
-
-    boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field);
-
-    boolean isValidInstanceFieldRead(CodeProcessor context, DexField field);
-
-    boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke);
-
-    boolean isValidNewInstance(CodeProcessor context, NewInstance invoke);
-
-    boolean isValidInitClass(CodeProcessor context, DexType clazz);
-
-    boolean isValidHolder(CodeProcessor context, DexType holder);
-
-    void patch(ApplyStrategy context, NewInstance newInstance);
-
-    void patch(ApplyStrategy context, InvokeMethod invoke);
-
-    void patch(ApplyStrategy context, InstanceGet instanceGet);
-
-    void patch(ApplyStrategy context, StaticGet staticGet);
-
-    void patch(ApplyStrategy context, InitClass initClass);
-
-    void patch(ApplyStrategy context, Argument argument);
-  }
-
-  // No-op strategy.
-  static final Strategy NoOp =
-      new Strategy() {
-        @Override
-        public LambdaGroup group() {
-          return null;
-        }
-
-        @Override
-        public boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field) {
-          return false;
-        }
-
-        @Override
-        public boolean isValidInstanceFieldRead(CodeProcessor context, DexField field) {
-          return false;
-        }
-
-        @Override
-        public boolean isValidStaticFieldWrite(CodeProcessor context, DexField field) {
-          return false;
-        }
-
-        @Override
-        public boolean isValidStaticFieldRead(CodeProcessor context, DexField field) {
-          return false;
-        }
-
-        @Override
-        public boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke) {
-          return false;
-        }
-
-        @Override
-        public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) {
-          return false;
-        }
-
-        @Override
-        public boolean isValidInitClass(CodeProcessor context, DexType clazz) {
-          return false;
-        }
-
-        @Override
-        public boolean isValidHolder(CodeProcessor context, DexType holder) {
-          return false;
-        }
-
-        @Override
-        public void patch(ApplyStrategy context, NewInstance newInstance) {
-          throw new Unreachable();
-        }
-
-        @Override
-        public void patch(ApplyStrategy context, InvokeMethod invoke) {
-          throw new Unreachable();
-        }
-
-        @Override
-        public void patch(ApplyStrategy context, InstanceGet instanceGet) {
-          throw new Unreachable();
-        }
-
-        @Override
-        public void patch(ApplyStrategy context, StaticGet staticGet) {
-          throw new Unreachable();
-        }
-
-        @Override
-        public void patch(ApplyStrategy context, InitClass initClass) {
-          throw new Unreachable();
-        }
-
-        @Override
-        public void patch(ApplyStrategy context, Argument argument) {
-          throw new Unreachable();
-        }
-      };
-
-  public final AppView<AppInfoWithLiveness> appView;
-  public final DexItemFactory factory;
-  public final Kotlin kotlin;
-
-  // Defines a factory providing a strategy for a lambda type, returns
-  // NoOp strategy if the type is not a lambda.
-  private final Function<DexType, Strategy> strategyProvider;
-
-  // Visitor for lambda type references seen in unexpected places. Either
-  // invalidates the lambda or asserts depending on the processing phase.
-  private final LambdaTypeVisitor lambdaChecker;
-
-  // Specify the context of the current instruction: method/code/blocks/instructions.
-  public final ProgramMethod method;
-  public final IRCode code;
-  public final ListIterator<BasicBlock> blocks;
-  private InstructionListIterator instructions;
-
-  // The inlining context (caller), if any.
-  private final ProgramMethod context;
-
-  CodeProcessor(
-      AppView<AppInfoWithLiveness> appView,
-      Function<DexType, Strategy> strategyProvider,
-      LambdaTypeVisitor lambdaChecker,
-      ProgramMethod method,
-      IRCode code) {
-    this(appView, strategyProvider, lambdaChecker, method, code, null);
-  }
-
-  CodeProcessor(
-      AppView<AppInfoWithLiveness> appView,
-      Function<DexType, Strategy> strategyProvider,
-      LambdaTypeVisitor lambdaChecker,
-      ProgramMethod method,
-      IRCode code,
-      ProgramMethod context) {
-    this.appView = appView;
-    this.strategyProvider = strategyProvider;
-    this.factory = appView.dexItemFactory();
-    this.kotlin = factory.kotlin;
-    this.lambdaChecker = lambdaChecker;
-    this.method = method;
-    this.code = code;
-    this.blocks = code.listIterator();
-    this.context = context;
-  }
-
-  public final InstructionListIterator instructions() {
-    assert instructions != null;
-    return instructions;
-  }
-
-  void processCode() {
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
-      instructions = block.listIterator(code);
-      while (instructions.hasNext()) {
-        instructions.next().accept(this);
-      }
-    }
-  }
-
-  private boolean shouldRewrite(DexField field) {
-    return shouldRewrite(field.holder);
-  }
-
-  private boolean shouldRewrite(DexMethod method) {
-    return shouldRewrite(method.holder);
-  }
-
-  private boolean shouldRewrite(DexType type) {
-    // Rewrite references to lambda classes if we are outside the class.
-    return type != (context != null ? context : method).getHolderType();
-  }
-
-  @Override
-  public Void handleInvoke(Invoke invoke) {
-    if (invoke.isInvokeNewArray()) {
-      lambdaChecker.accept(invoke.asInvokeNewArray().getReturnType());
-      return null;
-    }
-    if (invoke.isInvokeMultiNewArray()) {
-      lambdaChecker.accept(invoke.asInvokeMultiNewArray().getReturnType());
-      return null;
-    }
-    if (invoke.isInvokeCustom()) {
-      lambdaChecker.accept(invoke.asInvokeCustom().getCallSite());
-      return null;
-    }
-
-    InvokeMethod invokeMethod = invoke.asInvokeMethod();
-    Strategy strategy = strategyProvider.apply(invokeMethod.getInvokedMethod().holder);
-    if (strategy.isValidInvoke(this, invokeMethod)) {
-      // Invalidate signature, there still should not be lambda references.
-      lambdaChecker.accept(invokeMethod.getInvokedMethod().proto);
-      // Only rewrite references to lambda classes if we are outside the class.
-      if (shouldRewrite(invokeMethod.getInvokedMethod())) {
-        process(strategy, invokeMethod);
-      }
-      return null;
-    }
-
-    // For the rest invalidate any references.
-    if (invoke.isInvokePolymorphic()) {
-      lambdaChecker.accept(invoke.asInvokePolymorphic().getProto());
-    }
-    lambdaChecker.accept(invokeMethod.getInvokedMethod(), null);
-    return null;
-  }
-
-  @Override
-  public Void visit(NewInstance newInstance) {
-    Strategy strategy = strategyProvider.apply(newInstance.clazz);
-    if (strategy.isValidNewInstance(this, newInstance)) {
-      // Only rewrite references to lambda classes if we are outside the class.
-      if (shouldRewrite(newInstance.clazz)) {
-        process(strategy, newInstance);
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public Void visit(CheckCast checkCast) {
-    lambdaChecker.accept(checkCast.getType());
-    return null;
-  }
-
-  @Override
-  public Void visit(NewArrayEmpty newArrayEmpty) {
-    lambdaChecker.accept(newArrayEmpty.type);
-    return null;
-  }
-
-  @Override
-  public Void visit(ConstClass constClass) {
-    lambdaChecker.accept(constClass.getValue());
-    return null;
-  }
-
-  @Override
-  public Void visit(ConstMethodType constMethodType) {
-    lambdaChecker.accept(constMethodType.getValue());
-    return null;
-  }
-
-  @Override
-  public Void visit(ConstMethodHandle constMethodHandle) {
-    lambdaChecker.accept(constMethodHandle.getValue());
-    return null;
-  }
-
-  @Override
-  public Void visit(InstanceGet instanceGet) {
-    DexField field = instanceGet.getField();
-    Strategy strategy = strategyProvider.apply(field.holder);
-    if (strategy.isValidInstanceFieldRead(this, field)) {
-      if (shouldRewrite(field)) {
-        // Only rewrite references to lambda classes if we are outside the class.
-        process(strategy, instanceGet);
-      }
-    } else {
-      lambdaChecker.accept(field.type);
-    }
-
-    // We avoid fields with type being lambda class, it is possible for
-    // a lambda to capture another lambda, but we don't support it for now.
-    lambdaChecker.accept(field.type);
-    return null;
-  }
-
-  @Override
-  public Void visit(InstancePut instancePut) {
-    DexField field = instancePut.getField();
-    Strategy strategy = strategyProvider.apply(field.holder);
-    if (strategy.isValidInstanceFieldWrite(this, field)) {
-      if (shouldRewrite(field)) {
-        // Only rewrite references to lambda classes if we are outside the class.
-        process(strategy, instancePut);
-      }
-    } else {
-      lambdaChecker.accept(field.type);
-    }
-
-    // We avoid fields with type being lambda class, it is possible for
-    // a lambda to capture another lambda, but we don't support it for now.
-    lambdaChecker.accept(field.type);
-    return null;
-  }
-
-  @Override
-  public Void visit(StaticGet staticGet) {
-    DexField field = staticGet.getField();
-    Strategy strategy = strategyProvider.apply(field.holder);
-    if (strategy.isValidStaticFieldRead(this, field)) {
-      if (shouldRewrite(field)) {
-        // Only rewrite references to lambda classes if we are outside the class.
-        process(strategy, staticGet);
-      }
-    } else {
-      lambdaChecker.accept(field.type);
-      lambdaChecker.accept(field.holder);
-    }
-    return null;
-  }
-
-  @Override
-  public Void visit(StaticPut staticPut) {
-    DexField field = staticPut.getField();
-    Strategy strategy = strategyProvider.apply(field.holder);
-    if (strategy.isValidStaticFieldWrite(this, field)) {
-      if (shouldRewrite(field)) {
-        // Only rewrite references to lambda classes if we are outside the class.
-        process(strategy, staticPut);
-      }
-    } else {
-      lambdaChecker.accept(field.type);
-      lambdaChecker.accept(field.holder);
-    }
-    return null;
-  }
-
-  @Override
-  public Void visit(InitClass initClass) {
-    DexType clazz = initClass.getClassValue();
-    Strategy strategy = strategyProvider.apply(clazz);
-    if (strategy.isValidInitClass(this, clazz)) {
-      if (shouldRewrite(clazz)) {
-        // Only rewrite references to lambda classes if we are outside the class.
-        process(strategy, initClass);
-      }
-    } else {
-      lambdaChecker.accept(clazz);
-    }
-    return null;
-  }
-
-  @Override
-  public Void visit(Argument instruction) {
-    if (instruction.outValue() != code.getThis()) {
-      return null;
-    }
-    Strategy strategy = strategyProvider.apply(method.getHolderType());
-    if (strategy.isValidHolder(this, method.getHolderType())) {
-      if (shouldRewrite(method.getHolderType())) {
-        process(strategy, instruction);
-      }
-    }
-    return null;
-  }
-
-  abstract void process(Strategy strategy, InvokeMethod invokeMethod);
-
-  abstract void process(Strategy strategy, NewInstance newInstance);
-
-  abstract void process(Strategy strategy, InstancePut instancePut);
-
-  abstract void process(Strategy strategy, InstanceGet instanceGet);
-
-  abstract void process(Strategy strategy, StaticPut staticPut);
-
-  abstract void process(Strategy strategy, StaticGet staticGet);
-
-  abstract void process(Strategy strategy, InitClass initClass);
-
-  abstract void process(Strategy strategy, Argument argument);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
deleted file mode 100644
index cc01f83..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ /dev/null
@@ -1,237 +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.ir.optimize.lambda;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.google.common.collect.Lists;
-import com.google.common.io.BaseEncoding;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-// Represents a group of lambda classes which potentially can be represented
-// by the same lambda _group_ class. Each lambda class inside the group is
-// assigned an integer id.
-//
-// NOTE: access to lambdas in lambda group is NOT thread-safe.
-public abstract class LambdaGroup {
-  public final LambdaGroupId id;
-
-  // Lambda group class name. Is intended to be stable and uniques.
-  // In current implementation is generated in following way:
-  //       <optional-package>.-$$LambdaGroup$<HASH>
-  // with HASH generated based on names of the lambda class names
-  // of lambdas included in the group.
-  private DexType classType;
-
-  // Maps lambda classes belonging to the group into the index inside the
-  // group. Note usage of linked hash map to keep insertion ordering stable.
-  private final Map<DexType, LambdaInfo> lambdas = new LinkedHashMap<>();
-
-  public static class LambdaInfo {
-    public int id;
-    public final DexProgramClass clazz;
-
-    LambdaInfo(int id, DexProgramClass clazz) {
-      this.id = id;
-      this.clazz = clazz;
-    }
-
-    public DexProgramClass getLambdaClass() {
-      return clazz;
-    }
-  }
-
-  public LambdaGroup(LambdaGroupId id) {
-    this.id = id;
-  }
-
-  public final DexType getGroupClassType() {
-    assert classType != null;
-    return classType;
-  }
-
-  public final int size() {
-    return lambdas.size();
-  }
-
-  public final void forEachLambda(Consumer<LambdaInfo> action) {
-    assert verifyLambdaIds(false);
-    for (LambdaInfo info : lambdas.values()) {
-      action.accept(info);
-    }
-  }
-
-  public final boolean anyLambda(Predicate<LambdaInfo> predicate) {
-    assert verifyLambdaIds(false);
-    for (LambdaInfo info : lambdas.values()) {
-      if (predicate.test(info)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  final boolean shouldAddToMainDex(AppView<?> appView) {
-    // We add the group class to main index if any of the
-    // lambda classes it replaces is added to main index.
-    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
-    for (LambdaInfo info : lambdas.values()) {
-      if (mainDexClasses.contains(info.getLambdaClass())) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public final boolean containsLambda(DexType lambda) {
-    return lambdas.containsKey(lambda);
-  }
-
-  public final int lambdaId(DexType lambda) {
-    assert lambdas.containsKey(lambda);
-    return lambdas.get(lambda).id;
-  }
-
-  protected final List<DexEncodedField> lambdaCaptureFields(DexType lambda) {
-    assert lambdas.containsKey(lambda);
-    return lambdas.get(lambda).clazz.instanceFields();
-  }
-
-  protected final DexEncodedField lambdaSingletonField(DexType lambda) {
-    assert lambdas.containsKey(lambda);
-    List<DexEncodedField> fields = lambdas.get(lambda).clazz.staticFields();
-    assert fields.size() < 2;
-    return fields.size() == 0 ? null : fields.get(0);
-  }
-
-  // Contains less than 2 elements?
-  final boolean isTrivial() {
-    return lambdas.size() < 2;
-  }
-
-  final void add(DexProgramClass lambda) {
-    assert !lambdas.containsKey(lambda.type);
-    lambdas.put(lambda.type, new LambdaInfo(lambdas.size(), lambda));
-  }
-
-  final void remove(DexType lambda) {
-    assert lambdas.containsKey(lambda);
-    lambdas.remove(lambda);
-  }
-
-  final void compact() {
-    assert verifyLambdaIds(false);
-    int lastUsed = -1;
-    int lastSeen = -1;
-    for (Entry<DexType, LambdaInfo> entry : lambdas.entrySet()) {
-      int index = entry.getValue().id;
-      assert lastUsed <= lastSeen && lastSeen < index;
-      lastUsed++;
-      lastSeen = index;
-      if (lastUsed < index) {
-        entry.getValue().id = lastUsed;
-      }
-    }
-    assert verifyLambdaIds(true);
-  }
-
-  public abstract Strategy getCodeStrategy();
-
-  public abstract ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
-      Kotlin kotlin, AppInfoWithClassHierarchy appInfo);
-
-  // Package for a lambda group class to be created in.
-  protected abstract String getTypePackage();
-
-  protected abstract String getGroupSuffix();
-
-  final DexProgramClass synthesizeClass(
-      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback) {
-    assert classType == null;
-    assert verifyLambdaIds(true);
-    List<LambdaInfo> lambdas = Lists.newArrayList(this.lambdas.values());
-    classType =
-        appView
-            .dexItemFactory()
-            .createType(
-                "L"
-                    + getTypePackage()
-                    + "-$$LambdaGroup$"
-                    + getGroupSuffix()
-                    + createHash(lambdas)
-                    + ";");
-    return getBuilder(appView.dexItemFactory(), appView.options())
-        .synthesizeClass(appView, feedback);
-  }
-
-  protected abstract LambdaGroupClassBuilder<? extends LambdaGroup> getBuilder(
-      DexItemFactory factory, InternalOptions options);
-
-  private String createHash(List<LambdaInfo> lambdas) {
-    try {
-      ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-      ObjectOutputStream out = new ObjectOutputStream(bytes);
-
-      // We will generate SHA-1 hash of the list of lambda classes represented in the group.
-      for (LambdaInfo lambda : lambdas) {
-        DexString descriptor = lambda.clazz.type.descriptor;
-        out.writeInt(descriptor.size); // To avoid same-prefix problem
-        out.write(descriptor.content);
-      }
-      out.close();
-
-      MessageDigest digest = MessageDigest.getInstance("SHA-1");
-      digest.update(bytes.toByteArray());
-      return BaseEncoding.base64Url().omitPadding().encode(digest.digest());
-    } catch (NoSuchAlgorithmException | IOException ex) {
-      throw new Unreachable("Cannot get SHA-1 message digest");
-    }
-  }
-
-  private boolean verifyLambdaIds(boolean strict) {
-    int previous = -1;
-    for (LambdaInfo info : lambdas.values()) {
-      assert strict ? (previous + 1) == info.id : previous < info.id;
-      previous = info.id;
-    }
-    return true;
-  }
-
-  public static class LambdaStructureError extends Exception {
-    final boolean reportable;
-
-    public LambdaStructureError(String cause) {
-      this(cause, true);
-    }
-
-    public LambdaStructureError(String cause, boolean reportable) {
-      super(cause);
-      this.reportable = reportable;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
deleted file mode 100644
index 92f0972..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ /dev/null
@@ -1,85 +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.ir.optimize.lambda;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import java.util.Collections;
-import java.util.List;
-
-// Encapsulates lambda group class building logic and separates
-// it from the rest of lambda group functionality.
-public abstract class LambdaGroupClassBuilder<T extends LambdaGroup> {
-  protected final T group;
-  protected final DexItemFactory factory;
-  protected final String origin;
-
-  protected LambdaGroupClassBuilder(T group, DexItemFactory factory, String origin) {
-    this.group = group;
-    this.factory = factory;
-    this.origin = origin;
-  }
-
-  public final DexProgramClass synthesizeClass(
-      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback) {
-    DexType groupClassType = group.getGroupClassType();
-    DexType superClassType = getSuperClassType();
-    return new DexProgramClass(
-        groupClassType,
-        null,
-        new SynthesizedOrigin(origin, getClass()),
-        buildAccessFlags(),
-        superClassType,
-        buildInterfaces(),
-        factory.createString(origin),
-        null,
-        Collections.emptyList(),
-        buildEnclosingMethodAttribute(),
-        buildInnerClasses(),
-        buildClassSignature(),
-        DexAnnotationSet.empty(),
-        buildStaticFields(appView, feedback),
-        buildInstanceFields(),
-        buildDirectMethods(),
-        buildVirtualMethods(),
-        factory.getSkipNameValidationForTesting(),
-        // The name of the class is based on the hash of the content.
-        DexProgramClass::checksumFromType);
-  }
-
-  protected abstract DexType getSuperClassType();
-
-  protected abstract ClassAccessFlags buildAccessFlags();
-
-  protected abstract EnclosingMethodAttribute buildEnclosingMethodAttribute();
-
-  protected abstract List<InnerClassAttribute> buildInnerClasses();
-
-  protected abstract ClassSignature buildClassSignature();
-
-  protected abstract DexEncodedMethod[] buildVirtualMethods();
-
-  protected abstract DexEncodedMethod[] buildDirectMethods();
-
-  protected abstract DexEncodedField[] buildInstanceFields();
-
-  protected abstract DexEncodedField[] buildStaticFields(
-      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback);
-
-  protected abstract DexTypeList buildInterfaces();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupId.java
deleted file mode 100644
index 2c31541..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupId.java
+++ /dev/null
@@ -1,20 +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.ir.optimize.lambda;
-
-// Represents a lambda group identifier uniquely identifying the groups
-// of potentially mergeable lambdas.
-//
-// Implements hashCode/equals in a way that guarantees that if two lambda
-// classes has equal ids they belong to the same lambda group.
-public interface LambdaGroupId {
-  LambdaGroup createGroup();
-
-  @Override
-  int hashCode();
-
-  @Override
-  boolean equals(Object obj);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
deleted file mode 100644
index 5bccf4d4..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ /dev/null
@@ -1,738 +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.ir.optimize.lambda;
-
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
-import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
-import com.android.tools.r8.ir.code.Argument;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InitClass;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.StaticPut;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
-import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
-import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
-import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
-import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Deque;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.function.Function;
-
-// Merging lambda classes into single lambda group classes. There are three flavors
-// of lambdas we are dealing with:
-//   (a) lambda classes synthesized in desugaring, handles java lambdas
-//   (b) k-style lambda classes synthesized by kotlin compiler
-//   (c) j-style lambda classes synthesized by kotlin compiler
-//
-// Lambda merging is potentially applicable to all three of them, but
-// current implementation deals with both k- and j-style lambdas.
-//
-// In general we merge lambdas in 5 phases:
-//   1. collect all lambdas and compute group candidates. we do it synchronously
-//      and ensure that the order of lambda groups and lambdas inside each group
-//      is stable.
-//   2. analyze usages of lambdas and exclude lambdas with unexpected usage
-//      NOTE: currently we consider *all* usages outside the code invalid
-//      so we only need to patch method code when replacing the lambda class.
-//   3. exclude (invalidate) all lambda classes with usages we don't understand
-//      or support, compact the remaining lambda groups, remove trivial groups
-//      with less that 2 lambdas.
-//   4. replace lambda valid/supported class constructions with references to
-//      lambda group classes.
-//   5. synthesize group lambda classes.
-//
-public final class LambdaMerger {
-
-  private abstract static class Mode {
-
-    void rewriteCode(
-        ProgramMethod method,
-        IRCode code,
-        Inliner inliner,
-        ProgramMethod context,
-        InliningIRProvider provider) {}
-
-    void analyzeCode(ProgramMethod method, IRCode code) {}
-  }
-
-  private class AnalyzeMode extends Mode {
-
-    @Override
-    void analyzeCode(ProgramMethod method, IRCode code) {
-      new AnalysisStrategy(method, code).processCode();
-    }
-  }
-
-  private class ApplyMode extends Mode {
-
-    private final Map<DexProgramClass, LambdaGroup> lambdaGroups;
-    private final LambdaMergerOptimizationInfoFixer optimizationInfoFixer;
-
-    ApplyMode(
-        Map<DexProgramClass, LambdaGroup> lambdaGroups,
-        LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
-      this.lambdaGroups = lambdaGroups;
-      this.optimizationInfoFixer = optimizationInfoFixer;
-    }
-
-    @Override
-    void rewriteCode(
-        ProgramMethod method,
-        IRCode code,
-        Inliner inliner,
-        ProgramMethod context,
-        InliningIRProvider provider) {
-      LambdaGroup lambdaGroup = lambdaGroups.get(method.getHolder());
-      if (lambdaGroup == null) {
-        // Only rewrite the methods that have not been synthesized for the lambda group classes.
-        new ApplyStrategy(method, code, context, optimizationInfoFixer).processCode();
-        return;
-      }
-
-      if (method.getDefinition().isInitializer()) {
-        // Should not require rewriting.
-        return;
-      }
-
-      assert method.getDefinition().isNonPrivateVirtualMethod();
-      assert context == null;
-
-      Map<InvokeVirtual, InliningInfo> invokesToInline = new IdentityHashMap<>();
-      for (InvokeVirtual invoke : code.<InvokeVirtual>instructions(Instruction::isInvokeVirtual)) {
-        DexMethod invokedMethod = invoke.getInvokedMethod();
-        DexType holder = invokedMethod.holder;
-        if (lambdaGroup.containsLambda(holder)) {
-          // TODO(b/150685763): Check if we can use simpler lookup.
-          ResolutionResult resolution = appView.appInfo().resolveMethodOnClass(invokedMethod);
-          assert resolution.isSingleResolution();
-          ProgramMethod singleTarget =
-              resolution.asSingleResolution().getResolutionPair().asProgramMethod();
-          assert singleTarget != null;
-          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.getHolderType()));
-        }
-      }
-
-      assert invokesToInline.size() > 1
-          || appView.options().testing.verificationSizeLimitInBytesOverride > -1;
-
-      inliner.performForcedInlining(method, code, invokesToInline, provider, Timing.empty());
-    }
-  }
-
-  // Maps lambda into a group, only contains lambdas we decided to merge.
-  // NOTE: needs synchronization.
-  private final Map<DexType, LambdaGroup> lambdas = new IdentityHashMap<>();
-  // We use linked map to ensure stable ordering of the groups
-  // when they are processed sequentially.
-  // NOTE: needs synchronization.
-  private final Map<LambdaGroupId, LambdaGroup> groups = new LinkedHashMap<>();
-
-  // Since invalidating lambdas may happen concurrently we don't remove invalidated lambdas
-  // from groups (and `lambdas`) right away since the ordering may be important. Instead we
-  // collect invalidated lambdas and remove them from groups after analysis is done.
-  private final Set<DexType> invalidatedLambdas = Sets.newConcurrentHashSet();
-
-  // Methods which need to be patched to reference lambda group classes instead of the
-  // original lambda classes. The number of methods is expected to be small since there
-  // is a 1:1 relation between lambda and method it is defined in (unless such a method
-  // was inlined by either kotlinc or r8).
-  //
-  // Note that we don't track precisely lambda -> method mapping, so it may happen that
-  // we mark a method for further processing, and then invalidate the only lambda referenced
-  // from it. In this case we will reprocess method that does not need patching, but it
-  // should not be happening very frequently and we ignore possible overhead.
-  private final LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> methodsToReprocess =
-      LongLivedProgramMethodSetBuilder.createForSortedSet();
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final Kotlin kotlin;
-  private final DiagnosticsHandler reporter;
-
-  private Mode mode;
-
-  // Lambda visitor invalidating lambdas it sees.
-  private final LambdaTypeVisitor lambdaInvalidator;
-  // Lambda visitor throwing Unreachable on each lambdas it sees.
-  private final LambdaTypeVisitor lambdaChecker;
-
-  public LambdaMerger(AppView<AppInfoWithLiveness> appView) {
-    DexItemFactory factory = appView.dexItemFactory();
-    this.appView = appView;
-    this.kotlin = factory.kotlin;
-    this.reporter = appView.options().reporter;
-
-    this.lambdaInvalidator =
-        new LambdaTypeVisitor(factory, this::isMergeableLambda, this::invalidateLambda);
-    this.lambdaChecker =
-        new LambdaTypeVisitor(
-            factory,
-            this::isMergeableLambda,
-            type -> {
-              throw new Unreachable("Unexpected lambda " + type.toSourceString());
-            });
-  }
-
-  private void invalidateLambda(DexType lambda) {
-    invalidatedLambdas.add(lambda);
-  }
-
-  private synchronized boolean isMergeableLambda(DexType lambda) {
-    return lambdas.containsKey(lambda);
-  }
-
-  private synchronized LambdaGroup getLambdaGroup(DexType lambda) {
-    return lambdas.get(lambda);
-  }
-
-  private synchronized void queueForProcessing(ProgramMethod method) {
-    methodsToReprocess.add(method);
-  }
-
-  // Collect all group candidates and assign unique lambda ids inside each group.
-  // We do this before methods are being processed to guarantee stable order of
-  // lambdas inside each group.
-  public final void collectGroupCandidates(DexApplication app) {
-    // Collect lambda groups.
-    app.classes().stream()
-        .filter(cls -> !appView.appInfo().isPinned(cls.type))
-        .filter(
-            cls ->
-                appView.testing().kotlinLambdaMergerFactoryForClass.apply(cls) != null
-                    && KotlinLambdaGroupIdFactory.hasValidAnnotations(kotlin, cls)
-                    && !appView.appInfo().getClassToFeatureSplitMap().isInFeature(cls))
-        .sorted(Comparator.comparing(DexClass::getType)) // Ensure stable ordering.
-        .forEachOrdered(
-            lambda -> {
-              try {
-                KotlinLambdaGroupIdFactory lambdaGroupIdFactory =
-                    appView.testing().kotlinLambdaMergerFactoryForClass.apply(lambda);
-                LambdaGroupId id = lambdaGroupIdFactory.validateAndCreate(appView, kotlin, lambda);
-                LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup);
-                group.add(lambda);
-                lambdas.put(lambda.type, group);
-              } catch (LambdaStructureError error) {
-                // Intentionally empty.
-              }
-            });
-
-    // Remove trivial groups.
-    removeTrivialLambdaGroups();
-
-    assert mode == null;
-    mode = new AnalyzeMode();
-  }
-
-  /**
-   * Is called by IRConverter::rewriteCode. Performs different actions depending on the current
-   * mode.
-   *
-   * <ol>
-   *   <li>in ANALYZE mode analyzes invalid usages of lambda classes inside the method code,
-   *       invalidated such lambda classes, collects methods that need to be patched.
-   *   <li>in APPLY mode does nothing.
-   * </ol>
-   */
-  public final void analyzeCode(ProgramMethod method, IRCode code) {
-    if (mode != null) {
-      mode.analyzeCode(method, code);
-    }
-  }
-
-  /**
-   * Is called by IRConverter::rewriteCode. Performs different actions depending on the current
-   * mode.
-   *
-   * <ol>
-   *   <li>in ANALYZE mode does nothing.
-   *   <li>in APPLY mode patches the code to use lambda group classes, also asserts that there are
-   *       no more invalid lambda class references.
-   * </ol>
-   */
-  public final void rewriteCode(
-      ProgramMethod method, IRCode code, Inliner inliner, MethodProcessor methodProcessor) {
-    if (mode != null) {
-      mode.rewriteCode(
-          code.context(),
-          code,
-          inliner,
-          null,
-          new InliningIRProvider(appView, method, code, methodProcessor));
-    }
-  }
-
-  /**
-   * Similar to {@link #rewriteCode(ProgramMethod, IRCode, Inliner, MethodProcessor)}, but for
-   * rewriting code for inlining. The {@param context} is the caller that {@param method} is being
-   * inlined into.
-   */
-  public final void rewriteCodeForInlining(
-      ProgramMethod method, IRCode code, ProgramMethod context, InliningIRProvider provider) {
-    if (mode != null) {
-      mode.rewriteCode(method, code, null, context, provider);
-    }
-  }
-
-  public final void applyLambdaClassMapping(
-      DexApplication app,
-      IRConverter converter,
-      OptimizationFeedback feedback,
-      Builder<?> builder,
-      ExecutorService executorService,
-      GraphLens appliedGraphLens)
-      throws ExecutionException {
-    if (lambdas.isEmpty()) {
-      appView.setHorizontallyMergedLambdaClasses(HorizontallyMergedLambdaClasses.empty());
-      return;
-    }
-
-    // Analyse references from program classes. We assume that this optimization
-    // is only used for full program analysis and there are no classpath classes.
-    ThreadUtils.processItems(app.classes(), this::analyzeClass, executorService);
-
-    // Analyse more complex aspects of lambda classes including method code.
-    analyzeLambdaClassesStructure(executorService);
-
-    // Remove invalidated lambdas, compact groups to ensure
-    // sequential lambda ids, create group lambda classes.
-    BiMap<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups(feedback);
-
-    // Fixup optimization info to ensure that the optimization info does not refer to any merged
-    // lambdas.
-    LambdaMergerOptimizationInfoFixer optimizationInfoFixer =
-        new LambdaMergerOptimizationInfoFixer(lambdaGroupsClasses);
-    feedback.fixupOptimizationInfos(appView, executorService, optimizationInfoFixer);
-
-    // Switch to APPLY strategy.
-    this.mode = new ApplyMode(lambdaGroupsClasses.inverse(), optimizationInfoFixer);
-
-    FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
-        FieldAccessInfoCollectionModifier.builder();
-
-    // Add synthesized lambda group classes to the builder.
-    for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
-      DexProgramClass synthesizedClass = entry.getValue();
-      appView
-          .appInfo()
-          .addSynthesizedClass(synthesizedClass, entry.getKey().shouldAddToMainDex(appView));
-      builder.addSynthesizedClass(synthesizedClass);
-
-      synthesizedClass.forEachField(
-          field ->
-              fieldAccessInfoCollectionModifierBuilder
-                  .recordFieldReadInUnknownContext(field.getReference())
-                  .recordFieldWriteInUnknownContext(field.getReference()));
-
-      // Eventually, we need to process synthesized methods in the lambda group.
-      // Otherwise, abstract SynthesizedCode will be flown to Enqueuer.
-      // But that process should not see the holder. Otherwise, lambda calls in the main dispatch
-      // method became recursive calls via the lens rewriter. They should remain, then inliner
-      // will inline methods from mergee lambdas to the main dispatch method.
-      // Then, there is a dilemma: other sub optimizations trigger subtype lookup that will throw
-      // NPE if it cannot find the holder for this synthesized lambda group.
-      // One hack here is to mark those methods `processed` so that the lens rewriter is skipped.
-      synthesizedClass.forEachMethod(
-          encodedMethod -> encodedMethod.markProcessed(ConstraintWithTarget.NEVER));
-    }
-
-    // Record field accesses for synthesized fields.
-    fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
-
-    converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
-
-    // Rewrite lambda class references into lambda group class
-    // references inside methods from the processing queue.
-    rewriteLambdaReferences(converter, executorService, appliedGraphLens);
-    this.mode = null;
-
-    appView.setHorizontallyMergedLambdaClasses(new HorizontallyMergedLambdaClasses(lambdas));
-  }
-
-  private void analyzeLambdaClassesStructure(ExecutorService service) throws ExecutionException {
-    List<Future<?>> futures = new ArrayList<>();
-    for (LambdaGroup group : groups.values()) {
-      ThrowingConsumer<DexClass, LambdaStructureError> validator =
-          group.lambdaClassValidator(kotlin, appView.appInfo());
-      group.forEachLambda(
-          info ->
-              futures.add(
-                  service.submit(
-                      () -> {
-                        try {
-                          validator.accept(info.clazz);
-                        } catch (LambdaStructureError error) {
-                          ProguardConfiguration proguardConfiguration =
-                              appView.options().getProguardConfiguration();
-                          if (error.reportable
-                              && !proguardConfiguration
-                                  .getDontNotePatterns()
-                                  .matches(info.clazz.getType())) {
-                            reporter.info(
-                                new StringDiagnostic(
-                                    "Unexpected Kotlin lambda structure ["
-                                        + info.clazz.type.toSourceString()
-                                        + "]: "
-                                        + error.getMessage()));
-                          }
-                          invalidateLambda(info.clazz.type);
-                        }
-                      })));
-    }
-    ThreadUtils.awaitFutures(futures);
-  }
-
-  private BiMap<LambdaGroup, DexProgramClass> finalizeLambdaGroups(OptimizationFeedback feedback) {
-    for (DexType lambda : invalidatedLambdas) {
-      LambdaGroup group = lambdas.get(lambda);
-      assert group != null;
-      lambdas.remove(lambda);
-      group.remove(lambda);
-    }
-    invalidatedLambdas.clear();
-
-    // Remove new trivial lambdas.
-    removeTrivialLambdaGroups();
-
-    // Compact lambda groups, synthesize lambda group classes.
-    BiMap<LambdaGroup, DexProgramClass> result = HashBiMap.create();
-    for (LambdaGroup group : groups.values()) {
-      assert !group.isTrivial() : "No trivial group is expected here.";
-      group.compact();
-      DexProgramClass lambdaGroupClass = group.synthesizeClass(appView, feedback);
-      result.put(group, lambdaGroupClass);
-    }
-    return result;
-  }
-
-  private void removeTrivialLambdaGroups() {
-    Iterator<Entry<LambdaGroupId, LambdaGroup>> iterator = groups.entrySet().iterator();
-    while (iterator.hasNext()) {
-      Entry<LambdaGroupId, LambdaGroup> group = iterator.next();
-      if (group.getValue().isTrivial()) {
-        iterator.remove();
-        assert group.getValue().size() < 2;
-        group.getValue().forEachLambda(info -> this.lambdas.remove(info.clazz.type));
-      }
-    }
-  }
-
-  private void rewriteLambdaReferences(
-      IRConverter converter, ExecutorService executorService, GraphLens appliedGraphLens)
-      throws ExecutionException {
-    if (methodsToReprocess.isEmpty()) {
-      return;
-    }
-    SortedProgramMethodSet methods = methodsToReprocess.build(appView, appliedGraphLens);
-    converter.processMethodsConcurrently(methods, executorService);
-    assert methods.stream()
-        .map(DexClassAndMethod::getDefinition)
-        .allMatch(DexEncodedMethod::isProcessed);
-  }
-
-  private void analyzeClass(DexProgramClass clazz) {
-    lambdaInvalidator.accept(clazz.superType);
-    lambdaInvalidator.accept(clazz.interfaces);
-    lambdaInvalidator.accept(clazz.annotations());
-
-    for (DexEncodedField field : clazz.staticFields()) {
-      lambdaInvalidator.accept(field.annotations());
-      if (field.field.type != clazz.type) {
-        // Ignore static fields of the same type.
-        lambdaInvalidator.accept(field.field, clazz.type);
-      }
-    }
-    for (DexEncodedField field : clazz.instanceFields()) {
-      lambdaInvalidator.accept(field.annotations());
-      lambdaInvalidator.accept(field.field, clazz.type);
-    }
-
-    for (DexEncodedMethod method : clazz.methods()) {
-      lambdaInvalidator.accept(method.annotations());
-      lambdaInvalidator.accept(method.parameterAnnotationsList);
-      lambdaInvalidator.accept(method.method, clazz.type);
-    }
-  }
-
-  private Strategy strategyProvider(DexType type) {
-    LambdaGroup group = this.getLambdaGroup(type);
-    return group != null ? group.getCodeStrategy() : CodeProcessor.NoOp;
-  }
-
-  private final class AnalysisStrategy extends CodeProcessor {
-    private AnalysisStrategy(ProgramMethod method, IRCode code) {
-      super(
-          LambdaMerger.this.appView,
-          LambdaMerger.this::strategyProvider,
-          lambdaInvalidator,
-          method,
-          code);
-    }
-
-    @Override
-    void process(Strategy strategy, InvokeMethod invokeMethod) {
-      queueForProcessing(method);
-    }
-
-    @Override
-    void process(Strategy strategy, NewInstance newInstance) {
-      queueForProcessing(method);
-    }
-
-    @Override
-    void process(Strategy strategy, InstancePut instancePut) {
-      queueForProcessing(method);
-    }
-
-    @Override
-    void process(Strategy strategy, InstanceGet instanceGet) {
-      queueForProcessing(method);
-    }
-
-    @Override
-    void process(Strategy strategy, StaticPut staticPut) {
-      queueForProcessing(method);
-    }
-
-    @Override
-    void process(Strategy strategy, StaticGet staticGet) {
-      queueForProcessing(method);
-    }
-
-    @Override
-    void process(Strategy strategy, InitClass initClass) {
-      queueForProcessing(method);
-    }
-
-    @Override
-    void process(Strategy strategy, Argument argument) {
-      throw new Unreachable();
-    }
-  }
-
-  public final class ApplyStrategy extends CodeProcessor {
-
-    private final LambdaMergerOptimizationInfoFixer optimizationInfoFixer;
-
-    private final Set<Value> typeAffectedValues = Sets.newIdentityHashSet();
-
-    private ApplyStrategy(
-        ProgramMethod method,
-        IRCode code,
-        ProgramMethod context,
-        LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
-      super(
-          LambdaMerger.this.appView,
-          LambdaMerger.this::strategyProvider,
-          lambdaChecker,
-          method,
-          code,
-          context);
-      this.optimizationInfoFixer = optimizationInfoFixer;
-    }
-
-    public void recordTypeHasChanged(Value value) {
-      for (Value affectedValue : value.affectedValues()) {
-        if (typeMayHaveChanged(affectedValue)) {
-          typeAffectedValues.add(affectedValue);
-        }
-      }
-    }
-
-    @Override
-    void processCode() {
-      super.processCode();
-
-      if (typeAffectedValues.isEmpty()) {
-        return;
-      }
-
-      // Find all the transitively type affected values.
-      Set<Value> transitivelyTypeAffectedValues = SetUtils.newIdentityHashSet(typeAffectedValues);
-      Deque<Value> worklist = new ArrayDeque<>(typeAffectedValues);
-      while (!worklist.isEmpty()) {
-        Value value = worklist.pop();
-        assert typeMayHaveChanged(value);
-        assert transitivelyTypeAffectedValues.contains(value);
-
-        for (Value affectedValue : value.affectedValues()) {
-          if (typeMayHaveChanged(affectedValue)
-              && transitivelyTypeAffectedValues.add(affectedValue)) {
-            worklist.add(affectedValue);
-          }
-        }
-      }
-
-      // Update the types of these values if they refer to obsolete types. This is needed to be
-      // able to propagate the type information correctly, since lambda merging is neither a
-      // narrowing nor a widening.
-      for (Value value : transitivelyTypeAffectedValues) {
-        value.setType(value.getType().fixupClassTypeReferences(optimizationInfoFixer, appView));
-      }
-
-      // Filter out the type affected phis and destructively update the type of the phis. This is
-      // needed because narrowing does not work in presence of cyclic phis.
-      Set<Phi> typeAffectedPhis = Sets.newIdentityHashSet();
-      for (Value typeAffectedValue : transitivelyTypeAffectedValues) {
-        if (typeAffectedValue.isPhi()) {
-          typeAffectedPhis.add(typeAffectedValue.asPhi());
-        }
-      }
-      if (!typeAffectedPhis.isEmpty()) {
-        new DestructivePhiTypeUpdater(appView, optimizationInfoFixer)
-            .recomputeAndPropagateTypes(code, typeAffectedPhis);
-      }
-      assert code.verifyTypes(appView);
-    }
-
-    private boolean typeMayHaveChanged(Value value) {
-      return value.isPhi() || !value.definition.hasInvariantOutType();
-    }
-
-    @Override
-    void process(Strategy strategy, InvokeMethod invokeMethod) {
-      strategy.patch(this, invokeMethod);
-    }
-
-    @Override
-    void process(Strategy strategy, NewInstance newInstance) {
-      strategy.patch(this, newInstance);
-    }
-
-    @Override
-    void process(Strategy strategy, InstancePut instancePut) {
-      // Instance put should only appear in lambda class instance constructor,
-      // we should never get here since we never rewrite them.
-      throw new Unreachable();
-    }
-
-    @Override
-    void process(Strategy strategy, InstanceGet instanceGet) {
-      strategy.patch(this, instanceGet);
-    }
-
-    @Override
-    void process(Strategy strategy, StaticPut staticPut) {
-      // Static put should only appear in lambda class static initializer,
-      // we should never get here since we never rewrite them.
-      throw new Unreachable();
-    }
-
-    @Override
-    void process(Strategy strategy, StaticGet staticGet) {
-      strategy.patch(this, staticGet);
-    }
-
-    @Override
-    void process(Strategy strategy, InitClass initClass) {
-      strategy.patch(this, initClass);
-    }
-
-    @Override
-    void process(Strategy strategy, Argument argument) {
-      strategy.patch(this, argument);
-    }
-  }
-
-  private final class LambdaMergerOptimizationInfoFixer
-      implements Function<DexType, DexType>, OptimizationInfoFixer {
-
-    private final Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses;
-
-    LambdaMergerOptimizationInfoFixer(Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses) {
-      this.lambdaGroupsClasses = lambdaGroupsClasses;
-    }
-
-    @Override
-    public DexType apply(DexType type) {
-      LambdaGroup group = lambdas.get(type);
-      if (group != null) {
-        DexProgramClass clazz = lambdaGroupsClasses.get(group);
-        if (clazz != null) {
-          return clazz.type;
-        }
-      }
-      return type;
-    }
-
-    @Override
-    public void fixup(DexEncodedField field) {
-      FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
-      if (optimizationInfo.isMutableFieldOptimizationInfo()) {
-        optimizationInfo.asMutableFieldOptimizationInfo().fixupClassTypeReferences(this, appView);
-      } else {
-        assert optimizationInfo.isDefaultFieldOptimizationInfo();
-      }
-    }
-
-    @Override
-    public void fixup(DexEncodedMethod method) {
-      MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
-      if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
-        optimizationInfo
-            .asUpdatableMethodOptimizationInfo()
-            .fixupClassTypeReferences(this, appView);
-      } else {
-        assert optimizationInfo.isDefaultMethodOptimizationInfo();
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
deleted file mode 100644
index 9dce489..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
+++ /dev/null
@@ -1,146 +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.ir.optimize.lambda;
-
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-// Encapsulates the logic of visiting all lambda classes.
-final class LambdaTypeVisitor {
-  private final DexItemFactory factory;
-  private final Predicate<DexType> isLambdaType;
-  private final Consumer<DexType> onLambdaType;
-
-  LambdaTypeVisitor(DexItemFactory factory,
-      Predicate<DexType> isLambdaType, Consumer<DexType> onLambdaType) {
-    this.factory = factory;
-    this.isLambdaType = isLambdaType;
-    this.onLambdaType = onLambdaType;
-  }
-
-  void accept(DexCallSite callSite) {
-    accept(callSite.methodProto);
-    accept(callSite.bootstrapMethod);
-    for (DexValue value : callSite.bootstrapArgs) {
-      accept(value);
-    }
-  }
-
-  private void accept(DexValue value) {
-    switch (value.getValueKind()) {
-      case ARRAY:
-        for (DexValue elementValue : value.asDexValueArray().getValues()) {
-          accept(elementValue);
-        }
-        break;
-      case FIELD:
-        accept(value.asDexValueField().value, null);
-        break;
-      case METHOD:
-        accept(value.asDexValueMethod().value, null);
-        break;
-      case METHOD_HANDLE:
-        accept(value.asDexValueMethodHandle().value);
-        break;
-      case METHOD_TYPE:
-        accept(value.asDexValueMethodType().value);
-        break;
-      case TYPE:
-        accept(value.asDexValueType().value);
-        break;
-      default:
-        // Intentionally empty.
-    }
-  }
-
-  void accept(DexMethodHandle handle) {
-    if (handle.isFieldHandle()) {
-      accept(handle.asField(), null);
-    } else {
-      assert handle.isMethodHandle();
-      accept(handle.asMethod(), null);
-    }
-  }
-
-  void accept(DexField field, DexType holderToIgnore) {
-    accept(field.type);
-    if (holderToIgnore != field.holder) {
-      accept(field.holder);
-    }
-  }
-
-  void accept(DexMethod method, DexType holderToIgnore) {
-    if (holderToIgnore != method.holder) {
-      accept(method.holder);
-    }
-    accept(method.proto);
-  }
-
-  void accept(DexProto proto) {
-    accept(proto.returnType);
-    accept(proto.parameters);
-  }
-
-  void accept(DexTypeList types) {
-    for (DexType type : types.values) {
-      accept(type);
-    }
-  }
-
-  void accept(DexAnnotationSet annotationSet) {
-    for (DexAnnotation annotation : annotationSet.annotations) {
-      accept(annotation);
-    }
-  }
-
-  void accept(ParameterAnnotationsList parameterAnnotationsList) {
-    parameterAnnotationsList.forEachAnnotation(this::accept);
-  }
-
-  private void accept(DexAnnotation annotation) {
-    accept(annotation.annotation);
-  }
-
-  private void accept(DexEncodedAnnotation annotation) {
-    accept(annotation.type);
-    for (DexAnnotationElement element : annotation.elements) {
-      accept(element);
-    }
-  }
-
-  private void accept(DexAnnotationElement element) {
-    accept(element.value);
-  }
-
-  void accept(DexType type) {
-    if (type == null) {
-      return;
-    }
-    if (type.isPrimitiveType() || type.isVoidType() || type.isPrimitiveArrayType()) {
-      return;
-    }
-    if (type.isArrayType()) {
-      accept(type.toArrayElementType(factory));
-      return;
-    }
-    if (isLambdaType.test(type)) {
-      onLambdaType.accept(type);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
deleted file mode 100644
index 0eec9af..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
+++ /dev/null
@@ -1,69 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.google.common.collect.Lists;
-import java.util.List;
-
-final class ClassInitializerSourceCode extends SyntheticSourceCode {
-  private final DexItemFactory factory;
-  private final KotlinLambdaGroup group;
-
-  ClassInitializerSourceCode(
-      DexMethod method, DexItemFactory factory, KotlinLambdaGroup group, Position callerPosition) {
-    super(null, method, callerPosition);
-    assert method.proto.returnType == factory.voidType;
-    assert method.proto.parameters == DexTypeList.empty();
-    this.factory = factory;
-    this.group = group;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    DexType groupClassType = group.getGroupClassType();
-    DexMethod lambdaConstructorMethod = factory.createMethod(groupClassType,
-        factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
-
-    int instance = nextRegister(ValueType.OBJECT);
-    int lambdaId = nextRegister(ValueType.INT);
-    List<ValueType> argTypes = Lists.newArrayList(ValueType.OBJECT, ValueType.INT);
-    List<Integer> argRegisters = Lists.newArrayList(instance, lambdaId);
-
-    group.forEachLambda(
-        info -> {
-          DexType lambda = info.clazz.type;
-          if (group.isSingletonLambda(lambda)) {
-            int id = group.lambdaId(lambda);
-            add(builder -> builder.addNewInstance(instance, groupClassType));
-            add(builder -> builder.addConst(TypeElement.getInt(), lambdaId, id));
-            add(
-                builder ->
-                    builder.addInvoke(
-                        Type.DIRECT,
-                        lambdaConstructorMethod,
-                        lambdaConstructorMethod.proto,
-                        argTypes,
-                        argRegisters,
-                        false /* isInterface*/));
-            add(
-                builder ->
-                    builder.addStaticPut(instance, group.getSingletonInstanceField(factory, id)));
-          }
-        });
-
-    assert this.nextInstructionIndex() > 0 : "no single field initialized";
-    add(IRBuilder::addReturn);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
deleted file mode 100644
index 117ee3b..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
+++ /dev/null
@@ -1,238 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.google.common.collect.Lists;
-import java.util.List;
-import java.util.function.IntFunction;
-
-// Represents a j-style lambda group created to combine several lambda classes
-// generated by kotlin compiler for kotlin lambda expressions passed to java receivers,
-// like:
-//
-//      --- Java --------------------------------------------------------------
-//      public static void acceptString(Supplier<String> s) {
-//        // ...
-//      }
-//      -----------------------------------------------------------------------
-//      acceptString({ "A" })
-//      -----------------------------------------------------------------------
-//
-// Regular stateless j-style lambda class structure looks like below:
-// NOTE: stateless j-style lambdas do not always have INSTANCE field.
-//
-// -----------------------------------------------------------------------------------------------
-// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
-//                    Ljava/util/function/Supplier<Ljava/lang/String;>;
-// final class lambdas/LambdasKt$foo$4 implements java/util/function/Supplier {
-//
-//     public synthetic bridge get()Ljava/lang/Object;
-//
-//     public final get()Ljava/lang/String;
-//       @Lorg/jetbrains/annotations/NotNull;() // invisible
-//
-//     <init>()V
-//
-//     public final static Llambdas/LambdasKt$foo$4; INSTANCE
-//
-//     static <clinit>()V
-//
-//     OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-//     final static INNERCLASS lambdas/LambdasKt$foo$4 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Regular stateful j-style lambda class structure looks like below:
-//
-// -----------------------------------------------------------------------------------------------
-// signature <T:Ljava/lang/Object;>
-//                Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/String;>;
-// declaration: lambdas/LambdasKt$foo$5<T> implements java.util.function.Supplier<java.lang.String>
-// final class lambdas/LambdasKt$foo$5 implements java/util/function/Supplier  {
-//
-//     public synthetic bridge get()Ljava/lang/Object;
-//
-//     public final get()Ljava/lang/String;
-//       @Lorg/jetbrains/annotations/NotNull;() // invisible
-//
-//     <init>(Ljava/lang/String;I)V
-//
-//     final synthetic Ljava/lang/String; $m
-//     final synthetic I $v
-//
-//     OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-//     final static INNERCLASS lambdas/LambdasKt$foo$5 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Key j-style lambda class details:
-//   - extends java.lang.Object
-//   - implements *any* functional interface (Kotlin does not seem to support scenarios when
-//     lambda can implement multiple interfaces).
-//   - lambda class is created as an anonymous inner class
-//   - lambda class carries generic signature and kotlin metadata attribute
-//   - class instance fields represent captured values and have an instance constructor
-//     with matching parameters initializing them (see the second class above)
-//   - stateless lambda *may* be implemented as a singleton with a static field storing the
-//     only instance and initialized in static class constructor (see the first class above)
-//   - main lambda method usually matches an exact lambda signature and may have
-//     generic signature attribute and nullability parameter annotations
-//   - optional bridge method created to satisfy interface implementation and
-//     forwarding call to lambda main method
-//
-final class JStyleLambdaGroup extends KotlinLambdaGroup {
-  private JStyleLambdaGroup(GroupId id) {
-    super(id);
-  }
-
-  @Override
-  protected ClassBuilder getBuilder(DexItemFactory factory, InternalOptions options) {
-    return new ClassBuilder(factory, options, "java-style lambda group");
-  }
-
-  @Override
-  public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
-      Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
-    return new ClassValidator(kotlin, appInfo);
-  }
-
-  @Override
-  protected String getGroupSuffix() {
-    return "js$";
-  }
-
-  // Specialized group id.
-  final static class GroupId extends KotlinLambdaGroupId {
-    GroupId(
-        AppView<AppInfoWithLiveness> appView,
-        String capture,
-        DexType iface,
-        String pkg,
-        String signature,
-        DexEncodedMethod mainMethod,
-        InnerClassAttribute inner,
-        EnclosingMethodAttribute enclosing) {
-      super(appView, capture, iface, pkg, signature, mainMethod, inner, enclosing);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      return obj instanceof GroupId && computeEquals((KotlinLambdaGroupId) obj);
-    }
-
-    @Override
-    String getLambdaKindDescriptor() {
-      return "Kotlin j-style lambda group";
-    }
-
-    @Override
-    public LambdaGroup createGroup() {
-      return new JStyleLambdaGroup(this);
-    }
-  }
-
-  // Specialized class validator.
-  private class ClassValidator extends KotlinLambdaClassValidator {
-    ClassValidator(Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
-      super(kotlin, JStyleLambdaGroup.this, appInfo);
-    }
-
-    @Override
-    int getInstanceInitializerMaxSize(List<DexEncodedField> captures) {
-      return captures.size() + 2;
-    }
-
-    @Override
-    int validateInstanceInitializerEpilogue(
-        com.android.tools.r8.code.Instruction[] instructions, int index)
-        throws LambdaStructureError {
-      if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect
-              || instructions[index] instanceof com.android.tools.r8.code.InvokeDirectRange)
-          || instructions[index].getMethod() != kotlin.factory.objectMembers.constructor) {
-        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-      }
-      index++;
-      if (!(instructions[index] instanceof ReturnVoid)) {
-        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-      }
-      return index + 1;
-    }
-  }
-
-  // Specialized class builder.
-  private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<JStyleLambdaGroup> {
-    ClassBuilder(DexItemFactory factory, InternalOptions options, String origin) {
-      super(JStyleLambdaGroup.this, factory, options, origin);
-    }
-
-    @Override
-    protected DexType getSuperClassType() {
-      return factory.objectType;
-    }
-
-    @Override
-    SyntheticSourceCode createInstanceInitializerSourceCode(
-        DexType groupClassType, DexMethod initializerMethod, Position callerPosition) {
-      return new InstanceInitializerSourceCode(
-          factory,
-          groupClassType,
-          group.getLambdaIdField(factory),
-          id -> group.getCaptureField(factory, id),
-          initializerMethod,
-          callerPosition);
-    }
-  }
-
-  // Specialized instance initializer code.
-  private static final class InstanceInitializerSourceCode
-      extends KotlinInstanceInitializerSourceCode {
-    private final DexMethod objectInitializer;
-
-    InstanceInitializerSourceCode(
-        DexItemFactory factory,
-        DexType lambdaGroupType,
-        DexField idField,
-        IntFunction<DexField> fieldGenerator,
-        DexMethod method,
-        Position callerPosition) {
-      super(lambdaGroupType, idField, fieldGenerator, method, callerPosition);
-      this.objectInitializer = factory.objectMembers.constructor;
-    }
-
-    @Override
-    void prepareSuperConstructorCall(int receiverRegister) {
-      add(
-          builder ->
-              builder.addInvoke(
-                  Type.DIRECT,
-                  objectInitializer,
-                  objectInitializer.proto,
-                  Lists.newArrayList(ValueType.OBJECT),
-                  Lists.newArrayList(receiverRegister),
-                  false /* isInterface */));
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
deleted file mode 100644
index df72677..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ /dev/null
@@ -1,84 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public final class JStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
-  private static final JStyleLambdaGroupIdFactory INSTANCE = new JStyleLambdaGroupIdFactory();
-
-  private JStyleLambdaGroupIdFactory() {}
-
-  public static JStyleLambdaGroupIdFactory getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public LambdaGroupId validateAndCreate(
-      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
-      throws LambdaStructureError {
-    boolean accessRelaxed =
-        appView.options().getProguardConfiguration().isAccessModificationAllowed();
-
-    // Ignore ACC_SUPER.
-    ClassAccessFlags copy = lambda.accessFlags.copy();
-    copy.unsetSuper();
-    checkAccessFlags("class access flags", copy, PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
-
-    // Class and interface.
-    validateSuperclass(kotlin, lambda);
-    DexType iface = validateInterfaces(kotlin, lambda);
-
-    validateStaticFields(kotlin, lambda);
-    String captureSignature = validateInstanceFields(lambda, accessRelaxed);
-    validateDirectMethods(lambda);
-    DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
-    String genericSignature = validateAnnotations(appView, kotlin, lambda);
-    InnerClassAttribute innerClass = validateInnerClasses(lambda);
-
-    return new JStyleLambdaGroup.GroupId(
-        appView,
-        captureSignature,
-        iface,
-        accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
-        genericSignature,
-        mainMethod,
-        innerClass,
-        lambda.getEnclosingMethodAttribute());
-  }
-
-  @Override
-  void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
-    if (lambda.superType != kotlin.factory.objectType) {
-      throw new LambdaStructureError("implements " + lambda.superType.toSourceString() +
-          " instead of java.lang.Object");
-    }
-  }
-
-  @Override
-  DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
-    if (lambda.interfaces.size() == 0) {
-      throw new LambdaStructureError("does not implement any interfaces");
-    }
-    if (lambda.interfaces.size() > 1) {
-      throw new LambdaStructureError(
-          "implements more than one interface: " + lambda.interfaces.size());
-    }
-
-    // We don't validate that the interface is actually a functional interface,
-    // since it may be desugared, or optimized in any other way which should not
-    // affect lambda class merging.
-    return lambda.interfaces.values[0];
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
deleted file mode 100644
index cfae611..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ /dev/null
@@ -1,256 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.code.Const16;
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.google.common.collect.Lists;
-import java.util.List;
-import java.util.function.IntFunction;
-
-// Represents a k-style lambda group created to combine several lambda classes
-// generated by kotlin compiler for regular kotlin lambda expressions, like:
-//
-//      -----------------------------------------------------------------------
-//      fun foo(m: String, v: Int): () -> String {
-//        val lambda: (String, Int) -> String = { s, i -> s.substring(i) }
-//        return { "$m: $v" }
-//      }
-//      -----------------------------------------------------------------------
-//
-// Regular stateless k-style lambda class structure looks like below:
-// NOTE: stateless k-style lambdas do not always have INSTANCE field.
-//
-// -----------------------------------------------------------------------------------------------
-// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function2<
-//                          Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;>;
-// final class lambdas/LambdasKt$foo$lambda1$1
-//                  extends kotlin/jvm/internal/Lambda
-//                  implements kotlin/jvm/functions/Function2  {
-//
-//     public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-//
-//     public final invoke(Ljava/lang/String;I)Ljava/lang/String;
-//       @Lorg/jetbrains/annotations/NotNull;() // invisible
-//         @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
-//
-//     <init>()V
-//
-//     public final static Llambdas/LambdasKt$foo$lambda1$1; INSTANCE
-//
-//     static <clinit>()V
-//
-//     OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-//     final static INNERCLASS lambdas/LambdasKt$foo$lambda1$1 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Regular stateful k-style lambda class structure looks like below:
-//
-// -----------------------------------------------------------------------------------------------
-// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;
-// final class lambdas/LambdasKt$foo$1
-//                  extends kotlin/jvm/internal/Lambda
-//                  implements kotlin/jvm/functions/Function0  {
-//
-//     public synthetic bridge invoke()Ljava/lang/Object;
-//
-//     public final invoke()Ljava/lang/String;
-//       @Lorg/jetbrains/annotations/NotNull;() // invisible
-//
-//     <init>(Ljava/lang/String;I)V
-//
-//     final synthetic Ljava/lang/String; $m
-//     final synthetic I $v
-//
-//     OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-//     final static INNERCLASS lambdas/LambdasKt$foo$1 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Key k-style lambda class details:
-//   - extends kotlin.jvm.internal.Lambda
-//   - implements one of kotlin.jvm.functions.Function0..Function22, or FunctionN
-//     see: https://github.com/JetBrains/kotlin/blob/master/libraries/
-//                  stdlib/jvm/runtime/kotlin/jvm/functions/Functions.kt
-//     and: https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md
-//   - lambda class is created as an anonymous inner class
-//   - lambda class carries generic signature and kotlin metadata attribute
-//   - class instance fields represent captured values and have an instance constructor
-//     with matching parameters initializing them (see the second class above)
-//   - stateless lambda *may* be  implemented as a singleton with a static field storing the
-//     only instance and initialized in static class constructor (see the first class above)
-//   - main lambda method usually matches an exact lambda signature and may have
-//     generic signature attribute and nullability parameter annotations
-//   - optional bridge method created to satisfy interface implementation and
-//     forwarding call to lambda main method
-//
-final class KStyleLambdaGroup extends KotlinLambdaGroup {
-  private KStyleLambdaGroup(GroupId id) {
-    super(id);
-  }
-
-  @Override
-  protected ClassBuilder getBuilder(DexItemFactory factory, InternalOptions options) {
-    return new ClassBuilder(factory, options, "kotlin-style lambda group");
-  }
-
-  @Override
-  public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
-      Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
-    return new ClassValidator(kotlin, appInfo);
-  }
-
-  @Override
-  protected String getGroupSuffix() {
-    return "ks$";
-  }
-
-  // Specialized group id.
-  final static class GroupId extends KotlinLambdaGroupId {
-    GroupId(
-        AppView<AppInfoWithLiveness> appView,
-        String capture,
-        DexType iface,
-        String pkg,
-        String signature,
-        DexEncodedMethod mainMethod,
-        InnerClassAttribute inner,
-        EnclosingMethodAttribute enclosing) {
-      super(appView, capture, iface, pkg, signature, mainMethod, inner, enclosing);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-      return obj instanceof GroupId && computeEquals((KotlinLambdaGroupId) obj);
-    }
-
-    @Override
-    String getLambdaKindDescriptor() {
-      return "Kotlin k-style lambda group";
-    }
-
-    @Override
-    public LambdaGroup createGroup() {
-      return new KStyleLambdaGroup(this);
-    }
-  }
-
-  // Specialized class validator.
-  private final class ClassValidator extends KotlinLambdaClassValidator {
-    ClassValidator(Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
-      super(kotlin, KStyleLambdaGroup.this, appInfo);
-    }
-
-    @Override
-    int getInstanceInitializerMaxSize(List<DexEncodedField> captures) {
-      return captures.size() + 3;
-    }
-
-    @Override
-    int validateInstanceInitializerEpilogue(
-        com.android.tools.r8.code.Instruction[] instructions, int index)
-        throws LambdaStructureError {
-      if (!(instructions[index] instanceof Const4) &&
-          !(instructions[index] instanceof Const16)) {
-        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-      }
-      index++;
-      if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect
-              || instructions[index] instanceof com.android.tools.r8.code.InvokeDirectRange)
-          || instructions[index].getMethod() != kotlin.functional.lambdaInitializerMethod) {
-        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-      }
-      index++;
-      if (!(instructions[index] instanceof ReturnVoid)) {
-        throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-      }
-      return index + 1;
-    }
-  }
-
-  // Specialized class builder.
-  private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<KStyleLambdaGroup> {
-    ClassBuilder(DexItemFactory factory, InternalOptions options, String origin) {
-      super(KStyleLambdaGroup.this, factory, options, origin);
-    }
-
-    @Override
-    protected DexType getSuperClassType() {
-      return factory.kotlin.functional.lambdaType;
-    }
-
-    @Override
-    SyntheticSourceCode createInstanceInitializerSourceCode(
-        DexType groupClassType, DexMethod initializerMethod, Position callerPosition) {
-      return new InstanceInitializerSourceCode(
-          factory,
-          groupClassType,
-          group.getLambdaIdField(factory),
-          id -> group.getCaptureField(factory, id),
-          initializerMethod,
-          factory.kotlin.functional.getArity(id.iface),
-          callerPosition);
-    }
-  }
-
-  // Specialized instance initializer code.
-  private final static class InstanceInitializerSourceCode
-      extends KotlinInstanceInitializerSourceCode {
-    private final int arity;
-    private final DexMethod lambdaInitializer;
-
-    InstanceInitializerSourceCode(
-        DexItemFactory factory,
-        DexType lambdaGroupType,
-        DexField idField,
-        IntFunction<DexField> fieldGenerator,
-        DexMethod method,
-        int arity,
-        Position callerPosition) {
-      super(lambdaGroupType, idField, fieldGenerator, method, callerPosition);
-      this.arity = arity;
-      this.lambdaInitializer = factory.createMethod(factory.kotlin.functional.lambdaType,
-          factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
-    }
-
-    @Override
-    void prepareSuperConstructorCall(int receiverRegister) {
-      int arityRegister = nextRegister(ValueType.INT);
-      add(builder -> builder.addConst(TypeElement.getInt(), arityRegister, arity));
-      add(
-          builder ->
-              builder.addInvoke(
-                  Type.DIRECT,
-                  lambdaInitializer,
-                  lambdaInitializer.proto,
-                  Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
-                  Lists.newArrayList(receiverRegister, arityRegister),
-                  false /* isInterface */));
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
deleted file mode 100644
index cc212bf..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ /dev/null
@@ -1,85 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public final class KStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
-  private static final KStyleLambdaGroupIdFactory INSTANCE = new KStyleLambdaGroupIdFactory();
-
-  private KStyleLambdaGroupIdFactory() {}
-
-  public static KStyleLambdaGroupIdFactory getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public LambdaGroupId validateAndCreate(
-      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
-      throws LambdaStructureError {
-    boolean accessRelaxed =
-        appView.options().getProguardConfiguration().isAccessModificationAllowed();
-
-    // Ignore ACC_SUPER.
-    ClassAccessFlags copy = lambda.accessFlags.copy();
-    copy.unsetSuper();
-    checkAccessFlags("class access flags", copy, PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
-
-    // Class and interface.
-    validateSuperclass(kotlin, lambda);
-    DexType iface = validateInterfaces(kotlin, lambda);
-
-    validateStaticFields(kotlin, lambda);
-    String captureSignature = validateInstanceFields(lambda, accessRelaxed);
-    validateDirectMethods(lambda);
-    DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
-    String genericSignature = validateAnnotations(appView, kotlin, lambda);
-    InnerClassAttribute innerClass = validateInnerClasses(lambda);
-
-    return new KStyleLambdaGroup.GroupId(
-        appView,
-        captureSignature,
-        iface,
-        accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
-        genericSignature,
-        mainMethod,
-        innerClass,
-        lambda.getEnclosingMethodAttribute());
-  }
-
-  @Override
-  void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
-    if (lambda.superType != kotlin.functional.lambdaType) {
-      throw new LambdaStructureError("implements " + lambda.superType.toSourceString() +
-          " instead of kotlin.jvm.internal.Lambda");
-    }
-  }
-
-  @Override
-  DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
-    if (lambda.interfaces.size() == 0) {
-      throw new LambdaStructureError("does not implement any interfaces");
-    }
-    if (lambda.interfaces.size() > 1) {
-      throw new LambdaStructureError(
-          "implements more than one interface: " + lambda.interfaces.size());
-    }
-    DexType iface = lambda.interfaces.values[0];
-    if (!kotlin.functional.isFunctionInterface(iface)) {
-      throw new LambdaStructureError("implements " + iface.toSourceString() +
-          " instead of kotlin functional interface.");
-    }
-    return iface;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java
deleted file mode 100644
index 687b081..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java
+++ /dev/null
@@ -1,52 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import java.util.function.IntFunction;
-
-abstract class KotlinInstanceInitializerSourceCode extends SyntheticSourceCode {
-  private final DexField idField;
-  private final IntFunction<DexField> fieldGenerator;
-
-  KotlinInstanceInitializerSourceCode(
-      DexType lambdaGroupType,
-      DexField idField,
-      IntFunction<DexField> fieldGenerator,
-      DexMethod method,
-      Position callerPosition) {
-    super(lambdaGroupType, method, callerPosition);
-    this.idField = idField;
-    this.fieldGenerator = fieldGenerator;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    int receiverRegister = getReceiverRegister();
-
-    // Initialize lambda id field.
-    add(builder -> builder.addInstancePut(getParamRegister(0), receiverRegister, idField));
-
-    // Initialize capture values.
-    DexType[] values = proto.parameters.values;
-    for (int i = 1; i < values.length; i++) {
-      int index = i;
-      add(builder -> builder.addInstancePut(
-          getParamRegister(index), receiverRegister, fieldGenerator.apply(index - 1)));
-    }
-
-    // Call superclass constructor.
-    prepareSuperConstructorCall(receiverRegister);
-
-    add(IRBuilder::addReturn);
-  }
-
-  abstract void prepareSuperConstructorCall(int receiverRegister);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
deleted file mode 100644
index a956f84..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
+++ /dev/null
@@ -1,267 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.code.Format22c;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.Iput;
-import com.android.tools.r8.code.IputBoolean;
-import com.android.tools.r8.code.IputByte;
-import com.android.tools.r8.code.IputChar;
-import com.android.tools.r8.code.IputObject;
-import com.android.tools.r8.code.IputShort;
-import com.android.tools.r8.code.IputWide;
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.code.SputObject;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
-import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import java.util.List;
-
-// Encapsulates the logic of deep-checking of the lambda class assumptions.
-//
-// For k- and j-style lambdas we only check the code of class and instance
-// initializers to ensure that their code performs no unexpected actions:
-//
-//  (a) Class initializer is only present for stateless lambdas and does
-//      nothing expect instantiating the instance and storing it in
-//      static instance field.
-//
-//  (b) Instance initializers stores all captured values in proper capture
-//      fields and calls the super constructor passing arity to it.
-abstract class KotlinLambdaClassValidator
-    implements ThrowingConsumer<DexClass, LambdaStructureError> {
-
-  static final String LAMBDA_INIT_CODE_VERIFICATION_FAILED =
-      "instance initializer code verification failed";
-  private static final String LAMBDA_CLINIT_CODE_VERIFICATION_FAILED =
-      "static initializer code verification failed";
-
-  final Kotlin kotlin;
-  private final KotlinLambdaGroup group;
-  private final AppInfoWithClassHierarchy appInfo;
-
-  KotlinLambdaClassValidator(
-      Kotlin kotlin, KotlinLambdaGroup group, AppInfoWithClassHierarchy appInfo) {
-    this.kotlin = kotlin;
-    this.group = group;
-    this.appInfo = appInfo;
-  }
-
-  // Report a structure error.
-  final LambdaStructureError structureError(String s) {
-    return new LambdaStructureError(s, false);
-  }
-
-  @Override
-  public void accept(DexClass lambda) throws LambdaStructureError {
-    if (!CaptureSignature.getCaptureSignature(lambda.instanceFields()).equals(group.id().capture)) {
-      throw structureError("capture signature was modified");
-    }
-
-    DexEncodedMethod classInitializer = null;
-    DexEncodedMethod instanceInitializer = null;
-    for (DexEncodedMethod method : lambda.directMethods()) {
-      // We only check bodies of class and instance initializers since we don't expect to
-      // see any static or private methods and all virtual methods will be translated into
-      // same methods dispatching on lambda id to proper code.
-      if (method.isClassInitializer()) {
-        Code code = method.getCode();
-        if (!(group.isStateless() && group.isSingletonLambda(lambda.type))) {
-          throw structureError("static initializer on non-singleton lambda");
-        }
-        if (classInitializer != null || code == null || !code.isDexCode()) {
-          throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
-        }
-        validateStatelessLambdaClassInitializer(lambda, code.asDexCode());
-        classInitializer = method;
-
-      } else if (method.isInstanceInitializer()) {
-        Code code = method.getCode();
-        if (instanceInitializer != null || code == null || !code.isDexCode()) {
-          throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-        }
-        validateInstanceInitializer(lambda, code.asDexCode());
-        instanceInitializer = method;
-      }
-    }
-
-    if (group.isStateless() && group.isSingletonLambda(lambda.type) && (classInitializer == null)) {
-      throw structureError("missing static initializer on singleton lambda");
-    }
-
-    // This check is actually not required for lambda class merging, we only have to do
-    // this because group class method composition piggybacks on inlining which has
-    // assertions checking when we can inline force-inlined methods. So we double-check
-    // these assertion never triggers.
-    //
-    // NOTE: the real type name for lambda group class is not generated yet, but we
-    //       can safely use a fake one here.
-    DexType fakeLambdaGroupType = kotlin.factory.createType(
-        "L" + group.getTypePackage() + "-$$LambdaGroup$XXXX;");
-    WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
-        NopWhyAreYouNotInliningReporter.getInstance();
-    for (DexEncodedMethod method : lambda.virtualMethods()) {
-      if (!method.isInliningCandidate(
-          fakeLambdaGroupType, Reason.SIMPLE, appInfo, whyAreYouNotInliningReporter)) {
-        throw structureError("method " + method.method.toSourceString() +
-            " is not inline-able into lambda group class");
-      }
-    }
-  }
-
-  abstract int getInstanceInitializerMaxSize(List<DexEncodedField> captures);
-
-  abstract int validateInstanceInitializerEpilogue(
-      com.android.tools.r8.code.Instruction[] instructions, int index) throws LambdaStructureError;
-
-  private void validateInstanceInitializer(DexClass lambda, Code code)
-      throws LambdaStructureError {
-    List<DexEncodedField> captures = lambda.instanceFields();
-    com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
-    int index = 0;
-
-    if (instructions.length > getInstanceInitializerMaxSize(captures)) {
-      throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-    }
-
-    // Capture field assignments: go through captured fields in assumed order
-    // and ensure they are assigned values from appropriate parameters.
-    index = validateInstanceInitializerParameterMapping(captures, instructions, index);
-
-    // Check the constructor epilogue: a call to superclass constructor.
-    index = validateInstanceInitializerEpilogue(instructions, index);
-    assert index == instructions.length;
-  }
-
-  private int validateInstanceInitializerParameterMapping(
-      List<DexEncodedField> captures, Instruction[] instructions, int index)
-      throws LambdaStructureError {
-    int dead = 0;
-    int wideFieldsSeen = 0;
-    for (DexEncodedField field : captures) {
-      if (field.getOptimizationInfo().isDead()) {
-        dead++;
-        continue;
-      }
-      switch (field.field.type.toShorty()) {
-        case 'Z':
-          if (!(instructions[index] instanceof IputBoolean)
-              || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
-            throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-          }
-          break;
-
-        case 'B':
-          if (!(instructions[index] instanceof IputByte)
-              || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
-            throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-          }
-          break;
-
-        case 'S':
-          if (!(instructions[index] instanceof IputShort)
-              || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
-            throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-          }
-          break;
-
-        case 'C':
-          if (!(instructions[index] instanceof IputChar)
-              || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
-            throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-          }
-          break;
-
-        case 'I':
-        case 'F':
-          if (!(instructions[index] instanceof Iput)
-              || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
-            throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-          }
-          break;
-
-        case 'J':
-        case 'D':
-          if (!(instructions[index] instanceof IputWide)
-              || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
-            throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-          }
-          wideFieldsSeen++;
-          break;
-
-        case 'L':
-          if (!(instructions[index] instanceof IputObject)
-              || (instructions[index].getField() != field.field)
-              || (((Format22c) instructions[index]).A != (index + 1 + dead + wideFieldsSeen))) {
-            throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
-          }
-          break;
-
-        default:
-          throw new Unreachable();
-      }
-      index++;
-    }
-    return index;
-  }
-
-  private void validateStatelessLambdaClassInitializer(DexClass lambda, Code code)
-      throws LambdaStructureError {
-    assert group.isStateless() && group.isSingletonLambda(lambda.type);
-    com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
-    if (instructions.length != 4) {
-      throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
-    }
-    if (!(instructions[0] instanceof com.android.tools.r8.code.NewInstance)
-        || ((com.android.tools.r8.code.NewInstance) instructions[0]).getType() != lambda.type) {
-      throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
-    }
-    if (!(instructions[1] instanceof com.android.tools.r8.code.InvokeDirect
-            || instructions[1] instanceof com.android.tools.r8.code.InvokeDirectRange)
-        || !isLambdaInitializerMethod(lambda, instructions[1].getMethod())) {
-      throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
-    }
-    if (!(instructions[2] instanceof SputObject)
-        || !isLambdaSingletonField(lambda, instructions[2].getField())) {
-      throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
-    }
-    if (!(instructions[3] instanceof ReturnVoid)) {
-      throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
-    }
-  }
-
-  private boolean isLambdaSingletonField(DexClass lambda, DexField field) {
-    return field.type == lambda.type
-        && field.holder == lambda.type
-        && field.name == kotlin.functional.kotlinStyleLambdaInstanceName;
-  }
-
-  private boolean isLambdaInitializerMethod(DexClass holder, DexMethod method) {
-    return method.holder == holder.type
-        && method.name == kotlin.factory.constructorMethodName
-        && method.proto.parameters.isEmpty()
-        && method.proto.returnType == kotlin.factory.voidType;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
deleted file mode 100644
index 9ac5ca7..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
+++ /dev/null
@@ -1,54 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.MethodAccessFlags;
-
-interface KotlinLambdaConstants {
-  // Default lambda class flags.
-  ClassAccessFlags LAMBDA_CLASS_FLAGS =
-      ClassAccessFlags.fromDexAccessFlags(Constants.ACC_FINAL);
-  // Access-relaxed lambda class flags.
-  ClassAccessFlags PUBLIC_LAMBDA_CLASS_FLAGS =
-      ClassAccessFlags.fromDexAccessFlags(Constants.ACC_PUBLIC + Constants.ACC_FINAL);
-
-  // Default lambda class initializer flags.
-  MethodAccessFlags CLASS_INITIALIZER_FLAGS =
-      MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_STATIC, true);
-  // Default lambda class constructor flags.
-  MethodAccessFlags CONSTRUCTOR_FLAGS =
-      MethodAccessFlags.fromSharedAccessFlags(0, true);
-  // Access-relaxed lambda class constructor flags.
-  MethodAccessFlags CONSTRUCTOR_FLAGS_RELAXED =
-      MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, true);
-
-  // Default main lambda method flags.
-  MethodAccessFlags MAIN_METHOD_FLAGS =
-      MethodAccessFlags.fromSharedAccessFlags(
-          Constants.ACC_PUBLIC + Constants.ACC_FINAL, false);
-  // Default bridge lambda method flags.
-  MethodAccessFlags BRIDGE_METHOD_FLAGS =
-      MethodAccessFlags.fromSharedAccessFlags(
-          Constants.ACC_PUBLIC + Constants.ACC_SYNTHETIC + Constants.ACC_BRIDGE, false);
-  // Bridge lambda method flags after inliner.
-  MethodAccessFlags BRIDGE_METHOD_FLAGS_FIXED =
-      MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false);
-
-  // Default singleton instance folding field flags.
-  FieldAccessFlags SINGLETON_FIELD_FLAGS =
-      FieldAccessFlags.fromSharedAccessFlags(
-          Constants.ACC_PUBLIC + Constants.ACC_STATIC + Constants.ACC_FINAL);
-  // Default instance (lambda capture) field flags.
-  FieldAccessFlags CAPTURE_FIELD_FLAGS =
-      FieldAccessFlags.fromSharedAccessFlags(
-          Constants.ACC_FINAL + Constants.ACC_SYNTHETIC);
-  // access-relaxed instance (lambda capture) field flags.
-  FieldAccessFlags CAPTURE_FIELD_FLAGS_RELAXED =
-      FieldAccessFlags.fromSharedAccessFlags(
-          Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java
deleted file mode 100644
index fc3f2bd..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java
+++ /dev/null
@@ -1,85 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
-
-// Represents a lambda group created to combine several lambda classes generated
-// by kotlin compiler for either regular kotlin lambda expressions (k-style lambdas)
-// or lambda expressions created to implement java SAM interface.
-abstract class KotlinLambdaGroup extends LambdaGroup {
-  private final Strategy strategy = new KotlinLambdaGroupCodeStrategy(this);
-
-  KotlinLambdaGroup(LambdaGroupId id) {
-    super(id);
-  }
-
-  final KotlinLambdaGroupId id() {
-    return (KotlinLambdaGroupId) id;
-  }
-
-  final boolean isStateless() {
-    return id().capture.isEmpty();
-  }
-
-  final boolean hasAnySingletons() {
-    assert isStateless();
-    return anyLambda(info -> this.isSingletonLambda(info.clazz.type));
-  }
-
-  final boolean isSingletonLambda(DexType lambda) {
-    assert isStateless();
-    return lambdaSingletonField(lambda) != null;
-  }
-
-  // Field referencing singleton instance for a lambda with specified id.
-  final DexField getSingletonInstanceField(DexItemFactory factory, int id) {
-    return factory.createField(this.getGroupClassType(),
-        this.getGroupClassType(), factory.createString("INSTANCE$" + id));
-  }
-
-  @Override
-  protected String getTypePackage() {
-    String pkg = id().pkg;
-    return pkg.isEmpty() ? "" : (pkg + "/");
-  }
-
-  final DexProto createConstructorProto(DexItemFactory factory) {
-    String capture = id().capture;
-    DexType[] newParameters = new DexType[capture.length() + 1];
-    newParameters[0] = factory.intType; // Lambda id.
-    for (int i = 0; i < capture.length(); i++) {
-      newParameters[i + 1] = CaptureSignature.fieldType(factory, capture, i);
-    }
-    return factory.createProto(factory.voidType, newParameters);
-  }
-
-  final DexField getLambdaIdField(DexItemFactory factory) {
-    return factory.createField(this.getGroupClassType(), factory.intType, "$id$");
-  }
-
-  final int mapFieldIntoCaptureIndex(DexType lambda, DexField field) {
-    return CaptureSignature.mapFieldIntoCaptureIndex(
-        id().capture, lambdaCaptureFields(lambda), field);
-  }
-
-  final DexField getCaptureField(DexItemFactory factory, int index) {
-    assert index >= 0 && index < id().capture.length();
-    return factory.createField(this.getGroupClassType(),
-        CaptureSignature.fieldType(factory, id().capture, index), "$capture$" + index);
-  }
-
-  @Override
-  public Strategy getCodeStrategy() {
-    return strategy;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
deleted file mode 100644
index 977bdcf..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ /dev/null
@@ -1,354 +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.ir.optimize.lambda.kotlin;
-
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DexValue.DexValueNull;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.code.IntSwitch;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.TriConsumer;
-import com.google.common.collect.Lists;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-// Builds components of kotlin lambda group class.
-abstract class KotlinLambdaGroupClassBuilder<T extends KotlinLambdaGroup>
-    extends LambdaGroupClassBuilder<T> implements KotlinLambdaConstants {
-
-  final KotlinLambdaGroupId id;
-  final InternalOptions options;
-
-  KotlinLambdaGroupClassBuilder(
-      T group, DexItemFactory factory, InternalOptions options, String origin) {
-    super(group, factory, origin);
-    this.id = group.id();
-    this.options = options;
-  }
-
-  abstract SyntheticSourceCode createInstanceInitializerSourceCode(
-      DexType groupClassType, DexMethod initializerMethod, Position callerPosition);
-
-  // Always generate public final classes.
-  @Override
-  protected ClassAccessFlags buildAccessFlags() {
-    return PUBLIC_LAMBDA_CLASS_FLAGS;
-  }
-
-  // Take the attribute from the group, if exists.
-  @Override
-  protected EnclosingMethodAttribute buildEnclosingMethodAttribute() {
-    return id.enclosing;
-  }
-
-  // Take the attribute from the group, if exists.
-  @Override
-  protected List<InnerClassAttribute> buildInnerClasses() {
-    return !id.hasInnerClassAttribute()
-        ? Collections.emptyList()
-        : Lists.newArrayList(
-            new InnerClassAttribute(id.innerClassAccess, group.getGroupClassType(), null, null));
-  }
-
-  @Override
-  protected ClassSignature buildClassSignature() {
-    // Kotlin-style lambdas supported by the merged may only contain optional signature and
-    // kotlin metadata annotations. We remove the latter, but keep the signature if present.
-    return GenericSignature.parseClassSignature(
-        origin, id.signature, new SynthesizedOrigin(origin, getClass()), factory, options.reporter);
-  }
-
-  @Override
-  protected DexEncodedMethod[] buildVirtualMethods() {
-    // All virtual method are dispatched on $id$ field.
-    //
-    // For each of the virtual method name/signatures seen in the group
-    // we generate a correspondent method in lambda group class with same
-    // name/signatures dispatching the call to appropriate code taken
-    // from the lambda class.
-
-    Map<DexString, Map<DexProto, List<DexEncodedMethod>>> methods = collectVirtualMethods();
-    List<DexEncodedMethod> result = new ArrayList<>();
-
-    for (Entry<DexString, Map<DexProto, List<DexEncodedMethod>>> upper : methods.entrySet()) {
-      DexString methodName = upper.getKey();
-      for (Entry<DexProto, List<DexEncodedMethod>> inner : upper.getValue().entrySet()) {
-        // Methods for unique name/signature pair.
-        DexProto methodProto = inner.getKey();
-        List<DexEncodedMethod> implMethods = inner.getValue();
-
-        boolean isMainMethod =
-            id.mainMethodName == methodName && id.mainMethodProto == methodProto;
-
-        // Merging lambdas can introduce methods with too many instructions for the verifier on
-        // ART to give up statically verifying the method. We therefore split up the implementation
-        // methods and chain them with fallthrough:
-        // function <method>() {
-        //   switch(field.id) {
-        //     case 1:
-        //     case 2:
-        //     ...
-        //     case n:
-        //     default: <method$1>()
-        // }
-        //
-        // function <method$1>() {
-        //     case n + 1:
-        //     case n + 2:
-        //     ...
-        //     case n + m:
-        //     default: throw null
-        // }
-        IntBox counter = new IntBox(0);
-        Box<DexMethod> currentMethodBox =
-            new Box<>(factory.createMethod(group.getGroupClassType(), methodProto, methodName));
-        splitIntoGroupsBasedOnInstructionSize(
-            implMethods,
-            (implMethodsToAdd, methodsSoFar, methodsRemaining) -> {
-              assert currentMethodBox.isSet();
-              // For bridge methods we still use same PUBLIC FINAL as for the main method,
-              // since inlining removes BRIDGE & SYNTHETIC attributes from the bridge methods
-              // anyways and our new method is a product of inlining.
-              MethodAccessFlags accessFlags = MAIN_METHOD_FLAGS.copy();
-              DexMethod method = currentMethodBox.get();
-              DexMethod fallthrough =
-                  methodsRemaining
-                      ? factory.createMethod(
-                          group.getGroupClassType(),
-                          methodProto,
-                          methodName.toString() + "$" + counter.getAndIncrement())
-                      : null;
-              result.add(
-                  new DexEncodedMethod(
-                      method,
-                      accessFlags,
-                      MethodTypeSignature.noSignature(),
-                      isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
-                      isMainMethod
-                          ? id.mainMethodParamAnnotations
-                          : ParameterAnnotationsList.empty(),
-                      new SynthesizedCode(
-                          callerPosition ->
-                              new KotlinLambdaVirtualMethodSourceCode(
-                                  factory,
-                                  group.getGroupClassType(),
-                                  method,
-                                  group.getLambdaIdField(factory),
-                                  implMethodsToAdd,
-                                  fallthrough,
-                                  methodsSoFar,
-                                  callerPosition)),
-                      true));
-              currentMethodBox.set(fallthrough);
-            });
-        assert !currentMethodBox.isSet();
-      }
-    }
-    return result.toArray(DexEncodedMethod.EMPTY_ARRAY);
-  }
-
-  private void splitIntoGroupsBasedOnInstructionSize(
-      List<DexEncodedMethod> implMethods,
-      TriConsumer<List<DexEncodedMethod>, Integer, Boolean> consumer) {
-    List<DexEncodedMethod> methods = new ArrayList<>();
-    // Upper bound in DEX for reading the field for switching on the group id.
-    final int fieldLoadInstructionSize = 10;
-    int verificationSizeLimitInBytes = options.verificationSizeLimitInBytes();
-    int currentInstructionsSize = fieldLoadInstructionSize;
-    int implMethodsCommitted = 0;
-    for (DexEncodedMethod implMethod : implMethods) {
-      int packedSwitchPayloadSize =
-          (int)
-              (IntSwitch.basePackedSize(options.getInternalOutputMode())
-                  + IntSwitch.packedPayloadSize(options.getInternalOutputMode(), methods.size()));
-      Code code = implMethod.getCode();
-      // We only do lambda merging for DEX. If we started doing lambda merging for CF, we would
-      // have to compute a size.
-      assert code.isDexCode();
-      int codeSize = code.asDexCode().codeSizeInBytes();
-      int estimatedMethodSize = currentInstructionsSize + codeSize + packedSwitchPayloadSize;
-      if (methods.size() > 0 && estimatedMethodSize > verificationSizeLimitInBytes) {
-        consumer.accept(methods, implMethodsCommitted, true);
-        currentInstructionsSize = fieldLoadInstructionSize;
-        implMethodsCommitted += methods.size();
-        methods = new ArrayList<>();
-      }
-      methods.add(implMethod);
-      currentInstructionsSize += codeSize;
-    }
-    consumer.accept(methods, implMethodsCommitted, false);
-  }
-
-  // Build a map of virtual methods with unique name/proto pointing to a list of methods
-  // from lambda classes implementing appropriate logic. The indices in the list correspond
-  // to lambda ids. Note that some of the slots in the lists may be empty, indicating the
-  // fact that corresponding lambda does not have a virtual method with this signature.
-  private Map<DexString, Map<DexProto, List<DexEncodedMethod>>> collectVirtualMethods() {
-    Map<DexString, Map<DexProto, List<DexEncodedMethod>>> methods = new LinkedHashMap<>();
-    int size = group.size();
-    group.forEachLambda(info -> {
-      for (DexEncodedMethod method : info.clazz.virtualMethods()) {
-        List<DexEncodedMethod> list = methods
-            .computeIfAbsent(method.method.name,
-                k -> new LinkedHashMap<>())
-            .computeIfAbsent(method.method.proto,
-                k -> Lists.newArrayList(Collections.nCopies(size, null)));
-        assert list.get(info.id) == null;
-        list.set(info.id, method);
-      }
-    });
-    return methods;
-  }
-
-  @Override
-  protected DexEncodedMethod[] buildDirectMethods() {
-    // We only build an instance initializer and optional class
-    // initializer for stateless lambdas.
-
-    boolean needsSingletonInstances = group.isStateless() && group.hasAnySingletons();
-    DexType groupClassType = group.getGroupClassType();
-
-    DexEncodedMethod[] result = new DexEncodedMethod[needsSingletonInstances ? 2 : 1];
-    // Instance initializer mapping parameters into capture fields.
-    DexProto initializerProto = group.createConstructorProto(factory);
-    DexMethod initializerMethod =
-        factory.createMethod(groupClassType, initializerProto, factory.constructorMethodName);
-    result[0] =
-        new DexEncodedMethod(
-            initializerMethod,
-            CONSTRUCTOR_FLAGS_RELAXED, // always create access-relaxed constructor.
-            MethodTypeSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            ParameterAnnotationsList.empty(),
-            new SynthesizedCode(
-                callerPosition ->
-                    createInstanceInitializerSourceCode(
-                        groupClassType, initializerMethod, callerPosition)),
-            true);
-
-    // Static class initializer for stateless lambdas.
-    if (needsSingletonInstances) {
-      DexMethod method =
-          factory.createMethod(
-              groupClassType,
-              factory.createProto(factory.voidType),
-              factory.classConstructorMethodName);
-      result[1] =
-          new DexEncodedMethod(
-              method,
-              CLASS_INITIALIZER_FLAGS,
-              MethodTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  callerPosition ->
-                      new ClassInitializerSourceCode(method, factory, group, callerPosition)),
-              true);
-    }
-
-    return result;
-  }
-
-  @Override
-  protected DexEncodedField[] buildInstanceFields() {
-    // Lambda id field plus other fields defined by the capture signature.
-    String capture = id.capture;
-    int size = capture.length();
-    DexEncodedField[] result = new DexEncodedField[1 + size];
-
-    result[0] =
-        new DexEncodedField(
-            group.getLambdaIdField(factory),
-            CAPTURE_FIELD_FLAGS_RELAXED,
-            FieldTypeSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            null);
-
-    for (int id = 0; id < size; id++) {
-      result[id + 1] =
-          new DexEncodedField(
-              group.getCaptureField(factory, id),
-              CAPTURE_FIELD_FLAGS_RELAXED,
-              FieldTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              null);
-    }
-
-    return result;
-  }
-
-  @Override
-  protected DexEncodedField[] buildStaticFields(
-      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback) {
-    if (!group.isStateless()) {
-      return DexEncodedField.EMPTY_ARRAY;
-    }
-    // One field for each singleton lambda in the group.
-    List<DexEncodedField> result = new ArrayList<>(group.size());
-    group.forEachLambda(
-        info -> {
-          if (group.isSingletonLambda(info.clazz.type)) {
-            DexField field = group.getSingletonInstanceField(factory, info.id);
-            DexEncodedField encodedField =
-                new DexEncodedField(
-                    field,
-                    SINGLETON_FIELD_FLAGS,
-                    FieldTypeSignature.noSignature(),
-                    DexAnnotationSet.empty(),
-                    DexValueNull.NULL);
-            result.add(encodedField);
-
-            // Record that the field is definitely not null. It is guaranteed to be assigned in the
-            // class initializer of the enclosing class before it is read.
-            ClassTypeElement exactType =
-                ClassTypeElement.create(field.type, definitelyNotNull(), appView);
-            feedback.markFieldHasDynamicLowerBoundType(encodedField, exactType);
-            feedback.markFieldHasDynamicUpperBoundType(encodedField, exactType);
-          }
-        });
-    assert result.isEmpty() == !group.hasAnySingletons();
-    return result.toArray(DexEncodedField.EMPTY_ARRAY);
-  }
-
-  @Override
-  protected DexTypeList buildInterfaces() {
-    return new DexTypeList(new DexType[]{id.iface});
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
deleted file mode 100644
index 2c88700..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ /dev/null
@@ -1,310 +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.ir.optimize.lambda.kotlin;
-
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Argument;
-import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstNumber;
-import com.android.tools.r8.ir.code.InitClass;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.ir.optimize.lambda.CodeProcessor;
-import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.optimize.lambda.LambdaMerger.ApplyStrategy;
-import java.util.ArrayList;
-import java.util.List;
-
-// Defines the code processing strategy for kotlin lambdas.
-final class KotlinLambdaGroupCodeStrategy implements Strategy {
-  private final KotlinLambdaGroup group;
-
-  KotlinLambdaGroupCodeStrategy(KotlinLambdaGroup group) {
-    this.group = group;
-  }
-
-  @Override
-  public LambdaGroup group() {
-    return group;
-  }
-
-  @Override
-  public boolean isValidStaticFieldWrite(CodeProcessor context, DexField field) {
-    DexType lambda = field.holder;
-    assert group.containsLambda(lambda);
-    // Only support writes to singleton static field named 'INSTANCE' from lambda
-    // static class initializer.
-    return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName
-        && lambda == field.type
-        && context.method.getDefinition().isClassInitializer()
-        && context.method.getHolderType() == lambda;
-  }
-
-  @Override
-  public boolean isValidStaticFieldRead(CodeProcessor context, DexField field) {
-    DexType lambda = field.holder;
-    assert group.containsLambda(lambda);
-    // Support all reads of singleton static field named 'INSTANCE'.
-    return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName
-        && lambda == field.type;
-  }
-
-  @Override
-  public boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field) {
-    DexType lambda = field.holder;
-    DexMethod method = context.method.getReference();
-    assert group.containsLambda(lambda);
-    // Support writes to capture instance fields inside lambda constructor only.
-    return method.holder == lambda && context.method.getDefinition().isInstanceInitializer();
-  }
-
-  @Override
-  public boolean isValidInstanceFieldRead(CodeProcessor context, DexField field) {
-    assert group.containsLambda(field.holder);
-    // Support all reads from capture instance fields.
-    return true;
-  }
-
-  @Override
-  public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) {
-    // Only valid for stateful lambdas.
-    return !(group.isStateless() && group.isSingletonLambda(invoke.clazz));
-  }
-
-  @Override
-  public boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke) {
-    return isValidInitializerCall(context, invoke) || isValidVirtualCall(invoke);
-  }
-
-  private boolean isValidInitializerCall(CodeProcessor context, InvokeMethod invoke) {
-    DexMethod method = invoke.getInvokedMethod();
-    DexType lambda = method.holder;
-    assert group.containsLambda(lambda);
-    // Allow calls to a constructor from other classes if the lambda is singleton,
-    // otherwise allow such a call only from the same class static initializer.
-    boolean isSingletonLambda = group.isStateless() && group.isSingletonLambda(lambda);
-    return (isSingletonLambda == (context.method.getHolderType() == lambda))
-        && invoke.isInvokeDirect()
-        && context.factory.isConstructor(method)
-        && CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
-  }
-
-  private boolean isValidVirtualCall(InvokeMethod invoke) {
-    assert group.containsLambda(invoke.getInvokedMethod().holder);
-    // Allow all virtual calls.
-    return invoke.isInvokeVirtual();
-  }
-
-  @Override
-  public boolean isValidInitClass(CodeProcessor context, DexType clazz) {
-    assert group.containsLambda(clazz);
-    // Support all init class instructions.
-    return true;
-  }
-
-  @Override
-  public boolean isValidHolder(CodeProcessor context, DexType holder) {
-    assert group.containsLambda(holder);
-    return true;
-  }
-
-  @Override
-  public void patch(ApplyStrategy context, NewInstance newInstance) {
-    DexType oldType = newInstance.clazz;
-    DexType newType = group.getGroupClassType();
-
-    NewInstance patchedNewInstance =
-        new NewInstance(
-            newType,
-            context.code.createValue(
-                TypeElement.fromDexType(newType, definitelyNotNull(), context.appView)));
-    context.instructions().replaceCurrentInstruction(patchedNewInstance);
-
-    assert newType != oldType;
-    context.recordTypeHasChanged(patchedNewInstance.outValue());
-  }
-
-  @Override
-  public void patch(ApplyStrategy context, InvokeMethod invoke) {
-    assert group.containsLambda(invoke.getInvokedMethod().holder);
-    if (isValidInitializerCall(context, invoke)) {
-      patchInitializer(context, invoke.asInvokeDirect());
-    } else {
-      // Regular calls to virtual methods only need target method be replaced.
-      assert isValidVirtualCall(invoke);
-      DexMethod oldMethod = invoke.getInvokedMethod();
-      DexMethod newMethod = mapVirtualMethod(context.factory, oldMethod);
-
-      InvokeVirtual patchedInvokeVirtual =
-          new InvokeVirtual(
-              newMethod,
-              createValueForType(context, newMethod.proto.returnType),
-              invoke.arguments());
-      context.instructions().replaceCurrentInstruction(patchedInvokeVirtual);
-
-      // Otherwise, we need to record that the type of the out-value has changed.
-      assert newMethod.proto.returnType == oldMethod.proto.returnType;
-    }
-  }
-
-  @Override
-  public void patch(ApplyStrategy context, InstanceGet instanceGet) {
-    DexField oldField = instanceGet.getField();
-    DexField newField = mapCaptureField(context.factory, oldField.holder, oldField);
-
-    DexType oldFieldType = oldField.type;
-    DexType newFieldType = newField.type;
-
-    // We need to insert remapped values and in case the capture field
-    // of type Object optionally cast to expected field.
-    InstanceGet newInstanceGet =
-        new InstanceGet(createValueForType(context, newFieldType), instanceGet.object(), newField);
-    context.instructions().replaceCurrentInstruction(newInstanceGet);
-
-    if (oldFieldType.isPrimitiveType() || oldFieldType == context.factory.objectType) {
-      return;
-    }
-
-    // Since all captured values of non-primitive types are stored in fields of type
-    // java.lang.Object, we need to cast them to appropriate type to satisfy the verifier.
-    TypeElement castTypeLattice =
-        TypeElement.fromDexType(oldFieldType, maybeNull(), context.appView);
-    Value newValue = context.code.createValue(castTypeLattice, newInstanceGet.getLocalInfo());
-    newInstanceGet.outValue().replaceUsers(newValue);
-    CheckCast cast = new CheckCast(newValue, newInstanceGet.outValue(), oldFieldType);
-    cast.setPosition(newInstanceGet.getPosition());
-    context.instructions().add(cast);
-
-    // If the current block has catch handlers split the check cast into its own block.
-    // Since new cast is never supposed to fail, we leave catch handlers empty.
-    if (cast.getBlock().hasCatchHandlers()) {
-      context.instructions().previous();
-      context.instructions().split(context.code, 1, context.blocks);
-    }
-  }
-
-  @Override
-  public void patch(ApplyStrategy context, StaticGet staticGet) {
-    DexField oldField = staticGet.getField();
-    DexField newField = mapSingletonInstanceField(context.factory, oldField);
-
-    StaticGet patchedStaticGet =
-        new StaticGet(
-            context.code.createValue(
-                TypeElement.fromDexType(newField.type, maybeNull(), context.appView)),
-            newField);
-    context.instructions().replaceCurrentInstruction(patchedStaticGet);
-
-    assert newField.type != oldField.type;
-    context.recordTypeHasChanged(patchedStaticGet.outValue());
-  }
-
-  @Override
-  public void patch(ApplyStrategy context, InitClass initClass) {
-    InitClass pachedInitClass =
-        new InitClass(context.code.createValue(TypeElement.getInt()), group.getGroupClassType());
-    context.instructions().replaceCurrentInstruction(pachedInitClass);
-  }
-
-  @Override
-  public void patch(ApplyStrategy context, Argument argument) {
-    // An argument can be a direct operand to a phi that we potentially could not remove.
-    assert argument.getIndex() == 0;
-    // The argument value will be replaced by the invoke value.
-    argument
-        .outValue()
-        .setType(TypeElement.fromDexType(group.getGroupClassType(), maybeNull(), context.appView));
-    context.recordTypeHasChanged(argument.outValue());
-  }
-
-  private void patchInitializer(CodeProcessor context, InvokeDirect invoke) {
-    // Patching includes:
-    //  - change of methods
-    //  - adding lambda id as the first argument
-    //  - reshuffling other arguments (representing captured values)
-    //    according to capture signature of the group.
-
-    DexMethod method = invoke.getInvokedMethod();
-    DexType lambda = method.holder;
-
-    // Create constant with lambda id.
-    Value lambdaIdValue = context.code.createValue(TypeElement.getInt());
-    ConstNumber lambdaId = new ConstNumber(lambdaIdValue, group.lambdaId(lambda));
-    lambdaId.setPosition(invoke.getPosition());
-    context.instructions().previous();
-    context.instructions().add(lambdaId);
-
-    // Create a new InvokeDirect instruction.
-    Instruction next = context.instructions().next();
-    assert next == invoke;
-
-    DexMethod newTarget = mapInitializerMethod(context.factory, method);
-    List<Value> newArguments = mapInitializerArgs(lambdaIdValue, invoke.arguments(), method.proto);
-    context.instructions().replaceCurrentInstruction(
-        new InvokeDirect(newTarget, null /* no return value */, newArguments)
-    );
-  }
-
-  private Value createValueForType(CodeProcessor context, DexType returnType) {
-    return returnType == context.factory.voidType
-        ? null
-        : context.code.createValue(
-            TypeElement.fromDexType(returnType, maybeNull(), context.appView));
-  }
-
-  private List<Value> mapInitializerArgs(
-      Value lambdaIdValue, List<Value> oldArguments, DexProto proto) {
-    assert oldArguments.size() == proto.parameters.size() + 1;
-    List<Value> newArguments = new ArrayList<>();
-    newArguments.add(oldArguments.get(0)); // receiver
-    newArguments.add(lambdaIdValue); // lambda-id
-    List<Integer> reverseMapping =
-        CaptureSignature.getReverseCaptureMapping(proto.parameters.values);
-    for (int index : reverseMapping) {
-      // <original-capture-index> = mapping[<normalized-capture-index>]
-      newArguments.add(oldArguments.get(index + 1 /* after receiver */));
-    }
-    return newArguments;
-  }
-
-  // Map lambda class initializer into lambda group class initializer.
-  private DexMethod mapInitializerMethod(DexItemFactory factory, DexMethod method) {
-    assert factory.isConstructor(method);
-    assert CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
-    return factory.createMethod(group.getGroupClassType(),
-        group.createConstructorProto(factory), method.name);
-  }
-
-  // Map lambda class virtual method into lambda group class method.
-  private DexMethod mapVirtualMethod(DexItemFactory factory, DexMethod method) {
-    return factory.createMethod(group.getGroupClassType(), method.proto, method.name);
-  }
-
-  // Map lambda class capture field into lambda group class capture field.
-  private DexField mapCaptureField(DexItemFactory factory, DexType lambda, DexField field) {
-    return group.getCaptureField(factory, group.mapFieldIntoCaptureIndex(lambda, field));
-  }
-
-  // Map lambda class initializer into lambda group class initializer.
-  private DexField mapSingletonInstanceField(DexItemFactory factory, DexField field) {
-    return group.getSingletonInstanceField(factory, group.lambdaId(field.holder));
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
deleted file mode 100644
index 7f24d79..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
+++ /dev/null
@@ -1,149 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-abstract class KotlinLambdaGroupId implements LambdaGroupId {
-  private static final int MISSING_INNER_CLASS_ATTRIBUTE = -1;
-
-  private final int hash;
-
-  // Capture signature.
-  final String capture;
-  // Kotlin functional interface.
-  final DexType iface;
-  // Package (in case access relaxation is enabled root package is used).
-  final String pkg;
-  // Generic signature of the lambda class.
-  final String signature;
-
-  // Characteristics of the main lambda method. Kotlin generates one main method with
-  // the signature matching the lambda signature, and a bridge method (if needed)
-  // forwarding the call via interface to the main method. Main method may have
-  // generic signature and parameter annotations defining nullability.
-  //
-  // TODO: address cases when main method is removed after inlining.
-  // (the main method if created is supposed to be inlined, since it is always
-  //  only called from the bridge, and removed. In this case the method with
-  //  signature and annotations is removed allowing more lambda to be merged.)
-  final DexString mainMethodName;
-  final DexProto mainMethodProto;
-  final DexAnnotationSet mainMethodAnnotations;
-  final ParameterAnnotationsList mainMethodParamAnnotations;
-
-  final EnclosingMethodAttribute enclosing;
-
-  // Note that lambda classes are always created as _anonymous_ inner classes. We only
-  // need to store the fact that the class had this attribute and if it had store the
-  // access from InnerClassAttribute.
-  final int innerClassAccess;
-
-  KotlinLambdaGroupId(
-      AppView<AppInfoWithLiveness> appView,
-      String capture,
-      DexType iface,
-      String pkg,
-      String signature,
-      DexEncodedMethod mainMethod,
-      InnerClassAttribute inner,
-      EnclosingMethodAttribute enclosing) {
-    assert capture != null && iface != null && pkg != null && mainMethod != null;
-    assert inner == null || (inner.isAnonymous() && inner.getOuter() == null);
-    this.capture = capture;
-    this.iface = iface;
-    this.pkg = pkg;
-    this.signature = signature;
-    this.mainMethodName = mainMethod.method.name;
-    this.mainMethodProto = mainMethod.method.proto;
-    this.mainMethodAnnotations = mainMethod.liveAnnotations(appView);
-    this.mainMethodParamAnnotations = mainMethod.liveParameterAnnotations(appView);
-    this.innerClassAccess = inner != null ? inner.getAccess() : MISSING_INNER_CLASS_ATTRIBUTE;
-    this.enclosing = enclosing;
-    this.hash = computeHashCode();
-  }
-
-  final boolean hasInnerClassAttribute() {
-    return innerClassAccess != MISSING_INNER_CLASS_ATTRIBUTE;
-  }
-
-  @Override
-  public final int hashCode() {
-    return hash;
-  }
-
-  private int computeHashCode() {
-    int hash = capture.hashCode() * 7;
-    hash += iface.hashCode() * 17;
-    hash += pkg.hashCode() * 37;
-    hash += signature != null ? signature.hashCode() * 47 : 0;
-    hash += mainMethodName.hashCode() * 71;
-    hash += mainMethodProto.hashCode() * 89;
-    hash += mainMethodAnnotations != null ? mainMethodAnnotations.hashCode() * 101 : 0;
-    hash += mainMethodParamAnnotations != null ? mainMethodParamAnnotations.hashCode() * 113 : 0;
-    hash += innerClassAccess * 131;
-    hash += enclosing != null ? enclosing.hashCode() * 211 : 0;
-    return hash;
-  }
-
-  @Override
-  public abstract boolean equals(Object obj);
-
-  boolean computeEquals(KotlinLambdaGroupId other) {
-    return capture.equals(other.capture) &&
-        iface == other.iface &&
-        pkg.equals(other.pkg) &&
-        mainMethodName == other.mainMethodName &&
-        mainMethodProto == other.mainMethodProto &&
-        (mainMethodAnnotations == null ? other.mainMethodAnnotations == null
-            : mainMethodAnnotations.equals(other.mainMethodAnnotations)) &&
-        (mainMethodParamAnnotations == null ? other.mainMethodParamAnnotations == null
-            : mainMethodParamAnnotations.equals(other.mainMethodParamAnnotations)) &&
-        (signature == null ? other.signature == null : signature.equals(other.signature)) &&
-        innerClassAccess == other.innerClassAccess &&
-        (enclosing == null ? other.enclosing == null : enclosing.equals(other.enclosing));
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder builder = new StringBuilder(getLambdaKindDescriptor())
-        .append("\n  capture: ").append(capture)
-        .append("\n  interface: ").append(iface.descriptor)
-        .append("\n  package: ").append(pkg)
-        .append("\n  signature: ").append(signature)
-        .append("\n  main method name: ").append(mainMethodName.toString())
-        .append("\n  main method: ").append(mainMethodProto.toSourceString())
-        .append("\n  main annotations: ").append(mainMethodAnnotations)
-        .append("\n  main param annotations: ").append(mainMethodParamAnnotations)
-        .append("\n  inner: ")
-        .append(innerClassAccess == MISSING_INNER_CLASS_ATTRIBUTE ? "none" : innerClassAccess);
-    if (enclosing != null) {
-      if (enclosing.getEnclosingClass() != null) {
-        builder.append("\n  enclosingClass: ")
-            .append(enclosing.getEnclosingClass().descriptor);
-      } else {
-        builder.append("\n  enclosingMethod: ")
-            .append(enclosing.getEnclosingMethod().toSourceString());
-      }
-    }
-    return builder.toString();
-  }
-
-  abstract String getLambdaKindDescriptor();
-
-  @Override
-  public abstract LambdaGroup createGroup();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
deleted file mode 100644
index 472d861..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ /dev/null
@@ -1,244 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.AccessFlags;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.List;
-
-public abstract class KotlinLambdaGroupIdFactory implements KotlinLambdaConstants {
-  KotlinLambdaGroupIdFactory() {
-  }
-
-  public static KotlinLambdaGroupIdFactory getFactoryForClass(DexProgramClass clazz) {
-    if (clazz.getKotlinInfo().isSyntheticClass()
-        && clazz.getKotlinInfo().asSyntheticClass().isLambda()) {
-      if (clazz.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda()) {
-        return KStyleLambdaGroupIdFactory.getInstance();
-      }
-      assert clazz.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
-      return JStyleLambdaGroupIdFactory.getInstance();
-    }
-    return null;
-  }
-
-  // Creates a lambda group id for a Java or Kotlin style lambda. Never returns null, but may throw
-  // a LambdaStructureError if the lambda does not pass pre-requirements (mostly by not meeting
-  // high-level structure expectations).
-  //
-  // At this point we only perform high-level checks before qualifying the lambda as a candidate
-  // for merging and assigning lambda group id. We can NOT perform checks on method bodies since
-  // they may not be converted yet, we'll do that in KStyleLambdaClassValidator.
-  public abstract LambdaGroupId validateAndCreate(
-      AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
-      throws LambdaStructureError;
-
-  abstract void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError;
-
-  abstract DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError;
-
-  DexEncodedMethod validateVirtualMethods(DexClass lambda) throws LambdaStructureError {
-    DexEncodedMethod mainMethod = null;
-
-    for (DexEncodedMethod method : lambda.virtualMethods()) {
-      if (method.accessFlags.materialize() == MAIN_METHOD_FLAGS.materialize()) {
-        if (mainMethod != null) {
-          throw new LambdaStructureError("more than one main method found");
-        }
-        mainMethod = method;
-      } else {
-        checkAccessFlags("unexpected virtual method access flags",
-            method.accessFlags, BRIDGE_METHOD_FLAGS, BRIDGE_METHOD_FLAGS_FIXED);
-        checkDirectMethodAnnotations(method);
-      }
-    }
-
-    if (mainMethod == null) {
-      // Missing main method may be a result of tree shaking.
-      throw new LambdaStructureError("no main method found", false);
-    }
-    return mainMethod;
-  }
-
-  InnerClassAttribute validateInnerClasses(DexClass lambda) throws LambdaStructureError {
-    List<InnerClassAttribute> innerClasses = lambda.getInnerClasses();
-    if (innerClasses != null) {
-      for (InnerClassAttribute inner : innerClasses) {
-        if (inner.getInner() == lambda.type) {
-          if (!inner.isAnonymous()) {
-            throw new LambdaStructureError("is not anonymous");
-          }
-          return inner;
-        }
-      }
-    }
-    return null;
-  }
-
-  public static boolean hasValidAnnotations(Kotlin kotlin, DexClass lambda) {
-    for (DexAnnotation annotation : lambda.annotations().annotations) {
-      if (annotation.annotation.type == kotlin.factory.kotlinMetadataType) {
-        continue;
-      }
-      return false;
-    }
-    return true;
-  }
-
-  String validateAnnotations(AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
-      throws LambdaStructureError {
-    for (DexAnnotation annotation : lambda.liveAnnotations(appView).annotations) {
-      if (annotation.annotation.type == appView.dexItemFactory().kotlinMetadataType) {
-        // Ignore kotlin metadata on lambda classes. Metadata on synthetic
-        // classes exists but is not used in the current Kotlin version (1.2.21)
-        // and newly generated lambda _group_ class is not exactly a kotlin class.
-        continue;
-      }
-
-      assert !hasValidAnnotations(kotlin, lambda);
-      throw new LambdaStructureError(
-          "unexpected annotation: " + annotation.annotation.type.toSourceString());
-    }
-    assert hasValidAnnotations(kotlin, lambda);
-    return lambda.getClassSignature().toString();
-  }
-
-  void validateStaticFields(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
-    List<DexEncodedField> staticFields = lambda.staticFields();
-    if (staticFields.size() == 1) {
-      DexEncodedField field = staticFields.get(0);
-      if (field.field.name != kotlin.functional.kotlinStyleLambdaInstanceName ||
-          field.field.type != lambda.type || !field.accessFlags.isPublic() ||
-          !field.accessFlags.isFinal() || !field.accessFlags.isStatic()) {
-        throw new LambdaStructureError("unexpected static field " + field.toSourceString());
-      }
-      // No state if the lambda is a singleton.
-      if (lambda.instanceFields().size() > 0) {
-        throw new LambdaStructureError("has instance fields along with INSTANCE");
-      }
-      checkAccessFlags("static field access flags", field.accessFlags, SINGLETON_FIELD_FLAGS);
-      checkFieldAnnotations(field);
-
-    } else if (staticFields.size() > 1) {
-      throw new LambdaStructureError(
-          "only one static field max expected, found " + staticFields.size());
-    }
-  }
-
-  String validateInstanceFields(DexClass lambda, boolean accessRelaxed)
-      throws LambdaStructureError {
-    List<DexEncodedField> instanceFields = lambda.instanceFields();
-    for (DexEncodedField field : instanceFields) {
-      checkAccessFlags("capture field access flags", field.accessFlags,
-          accessRelaxed ? CAPTURE_FIELD_FLAGS_RELAXED : CAPTURE_FIELD_FLAGS);
-      checkFieldAnnotations(field);
-    }
-    return CaptureSignature.getCaptureSignature(instanceFields);
-  }
-
-  void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
-    for (DexEncodedMethod method : lambda.directMethods()) {
-      if (method.isClassInitializer()) {
-        // We expect to see class initializer only if there is a singleton field.
-        if (lambda.staticFields().size() != 1) {
-          throw new LambdaStructureError("has static initializer, but no singleton field");
-        }
-        checkAccessFlags(
-            "unexpected static initializer access flags",
-            method.accessFlags.getOriginalAccessFlags(),
-            CLASS_INITIALIZER_FLAGS);
-        checkDirectMethodAnnotations(method);
-      } else if (method.isStatic()) {
-        throw new LambdaStructureError(
-            "unexpected static method: " + method.method.toSourceString());
-      } else if (method.isInstanceInitializer()) {
-        // Lambda class is expected to have one constructor
-        // with parameters matching capture signature.
-        DexType[] parameters = method.method.proto.parameters.values;
-        List<DexEncodedField> instanceFields = lambda.instanceFields();
-        if (parameters.length != instanceFields.size()) {
-          throw new LambdaStructureError("constructor parameters don't match captured values.");
-        }
-        for (int i = 0; i < parameters.length; i++) {
-          // Kotlin compiler sometimes reshuffles the parameters so that their order
-          // in the constructor don't match order of capture fields. We could add
-          // support for it, but it happens quite rarely so don't bother for now.
-          if (parameters[i] != instanceFields.get(i).field.type) {
-            throw new LambdaStructureError(
-                "constructor parameters don't match captured values.", false);
-          }
-        }
-        checkAccessFlags("unexpected constructor access flags",
-            method.accessFlags, CONSTRUCTOR_FLAGS, CONSTRUCTOR_FLAGS_RELAXED);
-        checkDirectMethodAnnotations(method);
-
-      } else if (method.isPrivateMethod()) {
-        // TODO(b/135975229)
-        throw new LambdaStructureError("private method: " + method.method.toSourceString());
-      } else {
-        assert false;
-        throw new LambdaStructureError(
-            "unexpected method encountered: " + method.method.toSourceString());
-      }
-    }
-  }
-
-  private static void checkDirectMethodAnnotations(DexEncodedMethod method)
-      throws LambdaStructureError {
-    if (!method.annotations().isEmpty()) {
-      throw new LambdaStructureError(
-          "unexpected method annotations ["
-              + method.annotations().toSmaliString()
-              + "] on "
-              + method.method.toSourceString());
-    }
-    if (!method.parameterAnnotationsList.isEmpty()) {
-      throw new LambdaStructureError(
-          "unexpected method parameters annotations ["
-              + method.parameterAnnotationsList.toSmaliString()
-              + "] on "
-              + method.method.toSourceString());
-    }
-  }
-
-  private static void checkFieldAnnotations(DexEncodedField field) throws LambdaStructureError {
-    if (field.hasAnnotation()) {
-      throw new LambdaStructureError(
-          "unexpected field annotations ["
-              + field.annotations().toSmaliString()
-              + "] on "
-              + field.field.toSourceString());
-    }
-  }
-
-  @SafeVarargs
-  static <T extends AccessFlags> void checkAccessFlags(
-      String message, T actual, T... expected) throws LambdaStructureError {
-    checkAccessFlags(message, actual.materialize(), expected);
-  }
-
-  @SafeVarargs
-  private static <T extends AccessFlags> void checkAccessFlags(
-      String message, int actual, T... expected) throws LambdaStructureError {
-    for (T flag : expected) {
-      if (actual == flag.materialize()) {
-        return;
-      }
-    }
-    throw new LambdaStructureError(message);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
deleted file mode 100644
index 657a796..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
+++ /dev/null
@@ -1,122 +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.ir.optimize.lambda.kotlin;
-
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import java.util.ArrayList;
-import java.util.List;
-
-final class KotlinLambdaVirtualMethodSourceCode extends SyntheticSourceCode {
-  private final DexItemFactory factory;
-  private final DexField idField;
-  private final List<DexEncodedMethod> implMethods;
-  private final DexMethod fallThroughMethod;
-  private final int keyStart;
-
-  KotlinLambdaVirtualMethodSourceCode(
-      DexItemFactory factory,
-      DexType groupClass,
-      DexMethod method,
-      DexField idField,
-      List<DexEncodedMethod> implMethods,
-      DexMethod fallThroughMethod,
-      int keyStart,
-      Position callerPosition) {
-    super(groupClass, method, callerPosition);
-    this.factory = factory;
-    this.idField = idField;
-    this.implMethods = implMethods;
-    this.fallThroughMethod = fallThroughMethod;
-    this.keyStart = keyStart;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    int implMethodCount = implMethods.size();
-    // We generate a single switch on lambda $id value read from appropriate
-    // field, and for each lambda id generate a call to appropriate method of
-    // the lambda class. Since this methods are marked as 'force inline',
-    // they are inlined by the inliner.
-
-    // Return value register if needed.
-    DexType returnType = proto.returnType;
-    boolean returnsValue = returnType != factory.voidType;
-    ValueType retValueType = returnsValue ? ValueType.fromDexType(returnType) : null;
-    int retRegister = returnsValue ? nextRegister(retValueType) : -1;
-
-    // Lambda id register to switch on.
-    int idRegister = nextRegister(ValueType.INT);
-    add(builder -> builder.addInstanceGet(idRegister, getReceiverRegister(), idField));
-
-    // Switch on id.
-    // Note that 'keys' and 'offsets' are just captured here and filled
-    // in with values when appropriate basic blocks are created.
-    int[] keys = new int[implMethodCount];
-    int[] offsets = new int[implMethodCount];
-    int[] fallthrough = new int[1]; // Array as a container for late initialization.
-    int switchIndex = lastInstructionIndex();
-    add(builder -> builder.addSwitch(idRegister, keys, fallthrough[0], offsets),
-        builder -> endsSwitch(builder, switchIndex, fallthrough[0], offsets));
-
-    List<Value> arguments = new ArrayList<>(proto.parameters.values.length + 1);
-
-    fallthrough[0] = nextInstructionIndex();
-    if (fallThroughMethod == null) {
-      // Fallthrough treated as unreachable.
-      int nullRegister = nextRegister(ValueType.OBJECT);
-      add(builder -> builder.addNullConst(nullRegister));
-      add(builder -> builder.addThrow(nullRegister), endsBlock);
-    } else {
-      addMethodCall(fallThroughMethod, arguments, returnsValue, retRegister);
-    }
-
-    // Blocks for each lambda id.
-    for (int i = 0; i < implMethodCount; i++) {
-      keys[i] = keyStart + i;
-      DexEncodedMethod impl = implMethods.get(i);
-      if (impl == null) {
-        // Virtual method is missing in lambda class.
-        offsets[i] = fallthrough[0];
-        continue;
-      }
-      offsets[i] = nextInstructionIndex();
-      addMethodCall(impl.method, arguments, returnsValue, retRegister);
-    }
-  }
-
-  private void addMethodCall(
-      DexMethod method, List<Value> arguments, boolean returnsValue, int retRegister) {
-    // Emit fake call on `this` receiver.
-    add(
-        builder -> {
-          if (arguments.isEmpty()) {
-            arguments.add(builder.getReceiverValue());
-            List<Value> argumentValues = builder.getArgumentValues();
-            if (argumentValues != null) {
-              arguments.addAll(builder.getArgumentValues());
-            }
-          }
-          builder.addInvoke(
-              Invoke.Type.VIRTUAL, method, method.proto, arguments, false /* isInterface */);
-        });
-    // Handle return value if needed.
-    if (returnsValue) {
-      add(builder -> builder.addMoveResult(retRegister));
-      add(builder -> builder.addReturn(retRegister), endsBlock);
-    } else {
-      add(IRBuilder::addReturn, endsBlock);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 6b011a6..6fc09f5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 import static com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.getFlavour;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexClass;
@@ -35,11 +36,10 @@
       DexClass clazz,
       DexItemFactory factory,
       Reporter reporter,
-      boolean onlyProcessLambda,
       Consumer<DexEncodedMethod> keepByteCode) {
     DexAnnotation meta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
     if (meta != null) {
-      return getKotlinInfo(kotlin, clazz, factory, reporter, onlyProcessLambda, keepByteCode, meta);
+      return getKotlinInfo(kotlin, clazz, factory, reporter, keepByteCode, meta);
     }
     return NO_KOTLIN_INFO;
   }
@@ -49,14 +49,10 @@
       DexClass clazz,
       DexItemFactory factory,
       Reporter reporter,
-      boolean onlyProcessLambda,
       Consumer<DexEncodedMethod> keepByteCode,
       DexAnnotation annotation) {
     try {
       KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation);
-      if (onlyProcessLambda && !isSyntheticClassifiedLambda(kotlin, clazz, kMetadata)) {
-        return NO_KOTLIN_INFO;
-      }
       return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode);
     } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
       reporter.info(
@@ -77,12 +73,18 @@
     }
   }
 
-  private static boolean isSyntheticClassifiedLambda(
-      Kotlin kotlin, DexClass clazz, KotlinClassMetadata kMetadata) {
-    if (kMetadata instanceof SyntheticClass) {
-      SyntheticClass syntheticClass = (SyntheticClass) kMetadata;
-      return syntheticClass.isLambda()
-          && getFlavour(syntheticClass, clazz, kotlin) != Flavour.Unclassified;
+  public static boolean isLambda(AppView<?> appView, DexClass clazz) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Kotlin kotlin = dexItemFactory.kotlin;
+    DexAnnotation metadataAnnotation =
+        clazz.annotations().getFirstMatching(dexItemFactory.kotlinMetadataType);
+    if (metadataAnnotation != null) {
+      KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation.annotation);
+      if (kMetadata instanceof SyntheticClass) {
+        SyntheticClass syntheticClass = (SyntheticClass) kMetadata;
+        return syntheticClass.isLambda()
+            && getFlavour(syntheticClass, clazz, kotlin) != Flavour.Unclassified;
+      }
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index f9d2d18..4eef6cb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.google.common.collect.Sets;
@@ -25,6 +27,8 @@
 
 public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
 
+  private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
+
   private final AppView<?> appView;
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
   private final Set<DexType> prunedTypes;
@@ -57,23 +61,27 @@
       Set<DexProgramClass> localOrAnonymousClasses = Sets.newIdentityHashSet();
       enqueuer.forAllLiveClasses(
           clazz -> {
-            boolean onlyProcessLambdas = !keepMetadata || !enqueuer.isPinned(clazz.type);
             assert clazz.getKotlinInfo().isNoKotlinInformation();
-            clazz.setKotlinInfo(
-                KotlinClassMetadataReader.getKotlinInfo(
-                    appView.dexItemFactory().kotlin,
-                    clazz,
-                    appView.dexItemFactory(),
-                    appView.options().reporter,
-                    onlyProcessLambdas,
-                    method -> keepByteCodeFunctions.add(method.method)));
-            if (onlyProcessLambdas) {
+            if (!keepMetadata || !enqueuer.isPinned(clazz.getType())) {
+              if (KotlinClassMetadataReader.isLambda(appView, clazz)
+                  && clazz.hasClassInitializer()) {
+                feedback.classInitializerMayBePostponed(clazz.getClassInitializer());
+              }
+              clazz.setKotlinInfo(NO_KOTLIN_INFO);
               clazz.removeAnnotations(
                   annotation -> annotation.getAnnotationType() == kotlinMetadataType);
-            }
-            if (clazz.getEnclosingMethodAttribute() != null
-                && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
-              localOrAnonymousClasses.add(clazz);
+            } else {
+              clazz.setKotlinInfo(
+                  KotlinClassMetadataReader.getKotlinInfo(
+                      appView.dexItemFactory().kotlin,
+                      clazz,
+                      appView.dexItemFactory(),
+                      appView.options().reporter,
+                      method -> keepByteCodeFunctions.add(method.getReference())));
+              if (clazz.getEnclosingMethodAttribute() != null
+                  && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
+                localOrAnonymousClasses.add(clazz);
+              }
             }
           });
       appView.setCfByteCodePassThrough(keepByteCodeFunctions);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 1001ca9..44d6ede 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -139,7 +139,7 @@
           }
           final KotlinClassLevelInfo kotlinInfo =
               KotlinClassMetadataReader.getKotlinInfo(
-                  kotlin, clazz, factory, reporter, false, ConsumerUtils.emptyConsumer(), metadata);
+                  kotlin, clazz, factory, reporter, ConsumerUtils.emptyConsumer(), metadata);
           if (kotlinInfo == NO_KOTLIN_INFO) {
             return;
           }
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 ce1dadb..9161f4f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -61,6 +61,7 @@
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramDerivedContext;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -149,7 +150,6 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -177,6 +177,7 @@
     FINAL_TREE_SHAKING,
     INITIAL_MAIN_DEX_TRACING,
     FINAL_MAIN_DEX_TRACING,
+    GENERATE_MAIN_DEX_LIST,
     WHY_ARE_YOU_KEEPING;
 
     public boolean isInitialTreeShaking() {
@@ -199,8 +200,12 @@
       return this == FINAL_MAIN_DEX_TRACING;
     }
 
+    public boolean isGenerateMainDexList() {
+      return this == GENERATE_MAIN_DEX_LIST;
+    }
+
     public boolean isMainDexTracing() {
-      return isInitialMainDexTracing() || isFinalMainDexTracing();
+      return isInitialMainDexTracing() || isFinalMainDexTracing() || isGenerateMainDexList();
     }
 
     public boolean isWhyAreYouKeeping() {
@@ -345,11 +350,6 @@
   private final Map<DexProgramClass, Set<DexMethod>> reachableVirtualTargets =
       new IdentityHashMap<>();
 
-  /**
-   * A set of references we have reported missing to dedupe warnings.
-   */
-  private final Set<DexReference> reportedMissing = Sets.newIdentityHashSet();
-
   /** Collection of keep requirements for the program. */
   private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
 
@@ -398,15 +398,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();
@@ -424,7 +422,6 @@
         mode.isInitialTreeShaking() && options.forceProguardCompatibility
             ? ProguardCompatibilityActions.builder()
             : null;
-    this.previousMainDexTracingResult = previousMainDexTracingResult;
 
     if (mode.isInitialOrFinalTreeShaking()) {
       if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
@@ -549,8 +546,14 @@
     recordTypeReference(type, context, this::reportMissingClass);
   }
 
+  private void recordTypeReference(DexType type, ProgramDerivedContext context) {
+    recordTypeReference(type, context, this::reportMissingClass);
+  }
+
   private void recordTypeReference(
-      DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+      DexType type,
+      ProgramDerivedContext context,
+      BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
     if (type == null) {
       return;
     }
@@ -569,7 +572,9 @@
   }
 
   private void recordMethodReference(
-      DexMethod method, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+      DexMethod method,
+      ProgramDefinition context,
+      BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
     recordTypeReference(method.holder, context, missingClassConsumer);
     recordTypeReference(method.proto.returnType, context, missingClassConsumer);
     for (DexType type : method.proto.parameters.values) {
@@ -577,9 +582,9 @@
     }
   }
 
-  private void recordFieldReference(DexField field, ProgramDefinition context) {
-    recordTypeReference(field.holder, context);
-    recordTypeReference(field.type, context);
+  private void recordFieldReference(DexField field, ProgramDerivedContext context) {
+    recordTypeReference(field.getHolderType(), context);
+    recordTypeReference(field.getType(), context);
   }
 
   public DexEncodedMethod definitionFor(DexMethod method, ProgramDefinition context) {
@@ -595,15 +600,19 @@
   }
 
   private DexClass definitionFor(
-      DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+      DexType type,
+      ProgramDerivedContext context,
+      BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
     return internalDefinitionFor(type, context, missingClassConsumer);
   }
 
   private DexClass internalDefinitionFor(
-      DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+      DexType type,
+      ProgramDerivedContext context,
+      BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
     DexClass clazz = appInfo().definitionFor(type);
     if (clazz == null) {
-      missingClassConsumer.accept(type);
+      missingClassConsumer.accept(type, context);
       return null;
     }
     if (clazz.isNotProgramClass()) {
@@ -661,7 +670,7 @@
     }
     DexClass definition = appView.definitionFor(type);
     if (definition == null) {
-      reportMissingClass(type);
+      reportMissingClassWithoutContext(type);
       return;
     }
     if (definition.isProgramClass() || !liveNonProgramTypes.add(definition)) {
@@ -729,20 +738,6 @@
     items.forEachClass(this::enqueueRootClass);
   }
 
-  private void enqueueRootItem(Entry<DexReference, Set<ProguardKeepRuleBase>> root) {
-    DexReference reference = root.getKey();
-    Set<ProguardKeepRuleBase> rules = root.getValue();
-    if (reference.isDexField()) {
-      enqueueRootField(reference.asDexField(), rules);
-    } else if (reference.isDexMethod()) {
-      enqueueRootMethod(reference.asDexMethod(), rules);
-    } else if (reference.isDexType()) {
-      enqueueRootClass(reference.asDexType(), rules);
-    } else {
-      throw new Unreachable();
-    }
-  }
-
   // TODO(b/123923324): Verify that root items are present.
   private void enqueueRootClass(DexType type, Set<ProguardKeepRuleBase> rules) {
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
@@ -1441,11 +1436,10 @@
       return;
     }
 
-    // Must mark the field as targeted even if it does not exist.
-    markFieldAsTargeted(fieldReference, currentMethod);
-
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
+      // Must mark the field as targeted even if it does not exist.
+      markFieldAsTargeted(fieldReference, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
       return;
     }
@@ -1471,15 +1465,12 @@
       Log.verbose(getClass(), "Register Iget `%s`.", fieldReference);
     }
 
-    // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
-    // the field as live, if the holder is an interface.
-    if (appView.options().enableUnusedInterfaceRemoval) {
-      if (field.getReference() != fieldReference) {
-        markTypeAsLive(field.getHolder(), currentMethod);
-      }
+    if (field.getReference() != fieldReference) {
+      // Mark the initial resolution holder as live.
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
-    workList.enqueueMarkReachableFieldAction(
+    workList.enqueueMarkInstanceFieldAsReachableAction(
         field, currentMethod, KeepReason.fieldReferencedIn(currentMethod));
   }
 
@@ -1497,11 +1488,10 @@
       return;
     }
 
-    // Must mark the field as targeted even if it does not exist.
-    markFieldAsTargeted(fieldReference, currentMethod);
-
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
+      // Must mark the field as targeted even if it does not exist.
+      markFieldAsTargeted(fieldReference, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
       return;
     }
@@ -1527,16 +1517,13 @@
       Log.verbose(getClass(), "Register Iput `%s`.", fieldReference);
     }
 
-    // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
-    // the field as live, if the holder is an interface.
-    if (appView.options().enableUnusedInterfaceRemoval) {
-      if (field.getReference() != fieldReference) {
-        markTypeAsLive(field.getHolder(), currentMethod);
-      }
+    if (field.getReference() != fieldReference) {
+      // Mark the initial resolution holder as live.
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
     KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
-    workList.enqueueMarkReachableFieldAction(field, currentMethod, reason);
+    workList.enqueueMarkInstanceFieldAsReachableAction(field, currentMethod, reason);
   }
 
   void traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
@@ -1598,9 +1585,9 @@
     }
 
     if (field.getReference() != fieldReference) {
-      // Mark the non-rebound field access as targeted. Note that this should only be done if the
-      // field is not a dead proto field (in which case we bail-out above).
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Mark the initial resolution holder as live. Note that this should only be done if the field
+      // is not a dead proto field (in which case we bail-out above).
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
     markStaticFieldAsLive(field, currentMethod);
@@ -1663,9 +1650,9 @@
     }
 
     if (field.getReference() != fieldReference) {
-      // Mark the non-rebound field access as targeted. Note that this should only be done if the
-      // field is not a dead proto field (in which case we bail-out above).
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Mark the initial resolution holder as live. Note that this should only be done if the field
+      // is not a dead proto field (in which case we bail-out above).
+      markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
     markStaticFieldAsLive(field, currentMethod);
@@ -1733,6 +1720,13 @@
     markTypeAsLive(clazz, reason);
   }
 
+  private void markTypeAsLive(DexClass clazz, ProgramDefinition context) {
+    if (clazz.isProgramClass()) {
+      DexProgramClass programClass = clazz.asProgramClass();
+      markTypeAsLive(programClass, graphReporter.reportClassReferencedFrom(programClass, context));
+    }
+  }
+
   private void markTypeAsLive(DexProgramClass clazz, ProgramDefinition context) {
     markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, context));
   }
@@ -1754,14 +1748,20 @@
 
     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";
 
     // Mark types in inner-class attributes referenced.
-    for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
-      recordTypeReference(innerClassAttribute.getInner(), clazz, this::ignoreMissingClass);
-      recordTypeReference(innerClassAttribute.getOuter(), clazz, this::ignoreMissingClass);
+    {
+      BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer =
+          options.reportMissingClassesInInnerClassAttributes
+              ? this::reportMissingClass
+              : this::ignoreMissingClass;
+      for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+        recordTypeReference(innerClassAttribute.getInner(), clazz, missingClassConsumer);
+        recordTypeReference(innerClassAttribute.getOuter(), clazz, missingClassConsumer);
+      }
     }
 
     // Mark types in nest attributes referenced.
@@ -1777,11 +1777,15 @@
     EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
     if (enclosingMethodAttribute != null) {
       DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
+      BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer =
+          options.reportMissingClassesInEnclosingMethodAttribute
+              ? this::reportMissingClass
+              : this::ignoreMissingClass;
       if (enclosingMethod != null) {
-        recordMethodReference(enclosingMethod, clazz, this::ignoreMissingClass);
+        recordMethodReference(enclosingMethod, clazz, missingClassConsumer);
       } else {
         recordTypeReference(
-            enclosingMethodAttribute.getEnclosingClass(), clazz, this::ignoreMissingClass);
+            enclosingMethodAttribute.getEnclosingClass(), clazz, missingClassConsumer);
       }
     }
 
@@ -1951,8 +1955,8 @@
       DexProgramClass holder, ProgramDefinition annotatedItem, DexAnnotation annotation) {
     assert annotatedItem == holder || annotatedItem.asProgramMember().getHolder() == holder;
     assert !holder.isDexClass() || holder.asDexClass().isProgramClass();
-    DexType type = annotation.annotation.type;
-    recordTypeReference(type, holder);
+    DexType type = annotation.getAnnotationType();
+    recordTypeReference(type, annotatedItem);
     DexClass clazz = appView.definitionFor(type);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
@@ -1967,17 +1971,20 @@
     graphReporter.registerAnnotation(annotation, reason);
     AnnotationReferenceMarker referenceMarker =
         new AnnotationReferenceMarker(
-            annotation.annotation.type, holder, appView.dexItemFactory(), reason);
+            annotation.getAnnotationType(), annotatedItem, appView.dexItemFactory(), reason);
     annotation.annotation.collectIndexedItems(referenceMarker);
   }
 
   private FieldResolutionResult resolveField(DexField field, ProgramDefinition context) {
     // Record the references in case they are not program types.
-    recordFieldReference(field, context);
     FieldResolutionResult resolutionResult = appInfo.resolveField(field);
-    if (resolutionResult.isFailedOrUnknownResolution()) {
-      reportMissingField(field);
+    if (resolutionResult.isSuccessfulResolution()) {
+      recordFieldReference(
+          field, resolutionResult.getResolutionPair().asProgramDerivedContext(context));
+    } else {
+      assert resolutionResult.isFailedOrUnknownResolution();
       failedFieldResolutionTargets.add(field);
+      recordFieldReference(field, context);
     }
     return resolutionResult;
   }
@@ -1988,7 +1995,6 @@
     recordMethodReference(method, context);
     ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
     if (resolutionResult.isFailedResolution()) {
-      reportMissingMethod(method);
       markFailedMethodResolutionTargets(
           method, resolutionResult.asFailedResolution(), context, reason);
     }
@@ -2001,7 +2007,6 @@
     recordMethodReference(method, context);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
     if (resolutionResult.isFailedResolution()) {
-      reportMissingMethod(method);
       markFailedMethodResolutionTargets(
           method, resolutionResult.asFailedResolution(), context, reason);
     }
@@ -2142,7 +2147,6 @@
     DexEncodedMethod encodedMethod = clazz.lookupMethod(reference);
     if (encodedMethod == null) {
       failedMethodResolutionTargets.add(reference);
-      reportMissingMethod(reference);
       return;
     }
 
@@ -2243,7 +2247,11 @@
     missingClassesBuilder.ignoreNewMissingClass(clazz);
   }
 
-  private void reportMissingClass(DexType clazz) {
+  private void ignoreMissingClass(DexType clazz, ProgramDerivedContext context) {
+    ignoreMissingClass(clazz);
+  }
+
+  private void reportMissingClass(DexType clazz, ProgramDerivedContext context) {
     assert !mode.isFinalTreeShaking()
             || missingClassesBuilder.wasAlreadyMissing(clazz)
             || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
@@ -2251,19 +2259,19 @@
             // TODO(b/157107464): See if we can clean this up.
             || (initialPrunedTypes != null && initialPrunedTypes.contains(clazz))
         : "Unexpected missing class `" + clazz.toSourceString() + "`";
-    missingClassesBuilder.addNewMissingClass(clazz);
+    missingClassesBuilder.addNewMissingClass(clazz, context);
   }
 
-  private void reportMissingMethod(DexMethod method) {
-    if (Log.ENABLED && reportedMissing.add(method)) {
-      Log.verbose(Enqueuer.class, "Method `%s` is missing.", method);
-    }
-  }
-
-  private void reportMissingField(DexField field) {
-    if (Log.ENABLED && reportedMissing.add(field)) {
-      Log.verbose(Enqueuer.class, "Field `%s` is missing.", field);
-    }
+  @Deprecated
+  private void reportMissingClassWithoutContext(DexType clazz) {
+    assert !mode.isFinalTreeShaking()
+            || missingClassesBuilder.wasAlreadyMissing(clazz)
+            || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
+            || initialDeadProtoTypes.contains(clazz)
+            // TODO(b/157107464): See if we can clean this up.
+            || (initialPrunedTypes != null && initialPrunedTypes.contains(clazz))
+        : "Unexpected missing class `" + clazz.toSourceString() + "`";
+    missingClassesBuilder.legacyAddNewMissingClass(clazz);
   }
 
   private void markMethodAsTargeted(ProgramMethod method, KeepReason reason) {
@@ -2644,9 +2652,14 @@
     }
   }
 
+  private void markFieldAsTargeted(ProgramField field) {
+    markTypeAsLive(field.getHolder(), field);
+    markTypeAsLive(field.getType(), field);
+  }
+
   private void markFieldAsTargeted(DexField field, ProgramMethod context) {
-    markTypeAsLive(field.type, context);
-    markTypeAsLive(field.holder, context);
+    markTypeAsLive(field.getHolderType(), context);
+    markTypeAsLive(field.getType(), context);
   }
 
   private void markStaticFieldAsLive(ProgramField field, ProgramMethod context) {
@@ -2655,9 +2668,8 @@
 
   private void markStaticFieldAsLive(
       ProgramField field, ProgramDefinition context, KeepReason reason) {
-    // Mark the type live here, so that the class exists at runtime.
-    markTypeAsLive(field.getHolder(), field);
-    markTypeAsLive(field.getReference().type, field);
+    // Mark the field type and holder live here, so that they exist at runtime.
+    markFieldAsTargeted(field);
 
     markDirectAndIndirectClassInitializersAsLive(field.getHolder());
 
@@ -2687,8 +2699,8 @@
 
   private void markInstanceFieldAsLive(
       ProgramField field, ProgramDefinition context, KeepReason reason) {
-    markTypeAsLive(field.getHolder(), field);
-    markTypeAsLive(field.getType(), field);
+    markFieldAsTargeted(field);
+
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Adding instance field `%s` to live set.", field);
     }
@@ -2816,8 +2828,7 @@
         field.getHolder())) {
       markInstanceFieldAsLive(field, context, reason);
     } else {
-      markTypeAsLive(field.getHolder(), field);
-      markTypeAsLive(field.getReference().type, field);
+      markFieldAsTargeted(field);
 
       // Add the field to the reachable set if the type later becomes instantiated.
       reachableInstanceFields
@@ -3014,16 +3025,27 @@
   }
 
   // Returns the set of live types.
-  public Set<DexProgramClass> traceMainDex(
-      RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
+  public MainDexInfo traceMainDex(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
     assert analyses.isEmpty();
     assert mode.isMainDexTracing();
-    this.rootSet = rootSet;
+    this.rootSet = appView.getMainDexRootSet();
     // Translate the result of root-set computation into enqueuer actions.
     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 = appView.appInfo().getMainDexInfo().builder();
+    liveTypes.getItems().forEach(builder::addRoot);
+    if (mode.isInitialMainDexTracing()) {
+      liveMethods.getItems().forEach(method -> builder.addRoot(method.method));
+    } else {
+      assert appView.appInfo().getMainDexInfo().isTracedMethodRootsCleared()
+          || mode.isGenerateMainDexList();
+    }
+    new MainDexListBuilder(appView, builder.getRoots(), builder).run();
+    MainDexInfo previousMainDexInfo = appInfo.getMainDexInfo();
+    return builder.build(previousMainDexInfo);
   }
 
   public AppInfoWithLiveness traceApplication(
@@ -3155,8 +3177,6 @@
 
     Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
 
-    Map<DexType, DexProgramClass> syntheticProgramClasses = new IdentityHashMap<>();
-
     Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
 
     // Subset of live methods that need have keep requirements.
@@ -3171,7 +3191,6 @@
           desugaredMethods.isEmpty()
               && syntheticInstantiations.isEmpty()
               && liveMethods.isEmpty()
-              && syntheticProgramClasses.isEmpty()
               && syntheticClasspathClasses.isEmpty();
       assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
       return empty;
@@ -3187,11 +3206,6 @@
       assert old == null;
     }
 
-    void addProgramClass(DexProgramClass clazz) {
-      DexProgramClass old = syntheticProgramClasses.put(clazz.type, clazz);
-      assert old == null;
-    }
-
     void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().method;
       assert !liveMethods.containsKey(signature);
@@ -3206,13 +3220,12 @@
 
     void amendApplication(Builder appBuilder) {
       assert !isEmpty();
-      appBuilder.addProgramClasses(syntheticProgramClasses.values());
       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) {
@@ -3283,7 +3296,6 @@
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
     synthesizeBackports(additions);
-    synthesizeNestConstructor(additions);
     synthesizeTwrCloseResource(additions);
     if (additions.isEmpty()) {
       return;
@@ -3297,7 +3309,7 @@
               additions.amendApplication(appBuilder);
               return appBuilder.build();
             });
-    additions.amendMainDexClasses(appInfo.getMainDexClasses());
+    additions.amendMainDexClasses(appInfo.getMainDexInfo());
     appView.setAppInfo(appInfo);
     subtypingInfo = new SubtypingInfo(appView);
 
@@ -3372,16 +3384,6 @@
     }
   }
 
-  private void synthesizeNestConstructor(SyntheticAdditions additions) {
-    if (nestBasedAccessRewriter != null) {
-      DexProgramClass nestConstructor = nestBasedAccessRewriter.synthesizeNestConstructor();
-      if (nestConstructor != null) {
-        // TODO(b/177638147): use getSyntheticItems().createClass().
-        additions.addProgramClass(nestConstructor);
-      }
-    }
-  }
-
   private void synthesizeTwrCloseResource(SyntheticAdditions additions) {
     for (ProgramMethod method : methodsWithTwrCloseResource.values()) {
       twrCloseResourceRewriter.rewriteCf(method, additions::addLiveMethod);
@@ -3501,7 +3503,7 @@
         new AppInfoWithLiveness(
             appInfo.getSyntheticItems().commit(app),
             appInfo.getClassToFeatureSplitMap(),
-            appInfo.getMainDexClasses(),
+            appInfo.getMainDexInfo(),
             deadProtoTypes,
             appView.testing().enableExperimentalMissingClassesReporting
                 ? missingClassesBuilder.reportMissingClasses(appView)
@@ -4107,15 +4109,10 @@
       DexProgramClass clazz, Supplier<KeepReason> reasonSupplier) {
     assert forceProguardCompatibility;
 
-    if (appView.hasProguardCompatibilityActions()
-        && !appView.getProguardCompatibilityActions().isCompatInstantiated(clazz)) {
+    if (!addCompatInstantiatedClass(clazz)) {
       return;
     }
 
-    if (mode.isInitialTreeShaking()) {
-      proguardCompatibilityActionsBuilder.addCompatInstantiatedType(clazz);
-    }
-
     KeepReasonWitness witness = graphReporter.registerClass(clazz, reasonSupplier.get());
     if (clazz.isAnnotation()) {
       markTypeAsLive(clazz, witness);
@@ -4133,6 +4130,25 @@
     }
   }
 
+  private boolean addCompatInstantiatedClass(DexProgramClass clazz) {
+    assert forceProguardCompatibility;
+
+    // During the first round of tree shaking, we compat-instantiate all classes referenced from
+    // check-cast, const-class, and instance-of instructions.
+    if (mode.isInitialTreeShaking()) {
+      proguardCompatibilityActionsBuilder.addCompatInstantiatedType(clazz);
+      return true;
+    }
+
+    assert proguardCompatibilityActionsBuilder == null;
+
+    // Otherwise, we only compat-instantiate classes referenced from check-cast, const-class, and
+    // instance-of instructions that were also compat-instantiated during the first round of tree
+    // shaking.
+    return appView.hasProguardCompatibilityActions()
+        && appView.getProguardCompatibilityActions().isCompatInstantiated(clazz);
+  }
+
   private void markMethodAsLiveWithCompatRule(ProgramMethod method) {
     enqueueMarkMethodLiveAction(method, method, graphReporter.reportCompatKeepMethod(method));
   }
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..cfce371 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,25 @@
       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 createForGenerateMainDexList(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
+      SubtypingInfo subtypingInfo,
+      GraphConsumer keptGraphConsumer) {
+    return new Enqueuer(
+        appView, executorService, subtypingInfo, keptGraphConsumer, Mode.GENERATE_MAIN_DEX_LIST);
   }
 
   public static Enqueuer createForWhyAreYouKeeping(
@@ -82,11 +69,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/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index b403b54..01e415b 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -55,13 +55,13 @@
     }
   }
 
-  static class MarkReachableFieldAction extends EnqueuerAction {
+  static class MarkInstanceFieldAsReachableAction extends EnqueuerAction {
     private final ProgramField field;
     // TODO(b/175854431): Avoid pushing context on worklist.
     private final ProgramDefinition context;
     private final KeepReason reason;
 
-    public MarkReachableFieldAction(
+    public MarkInstanceFieldAsReachableAction(
         ProgramField field, ProgramDefinition context, KeepReason reason) {
       this.field = field;
       this.context = context;
@@ -276,9 +276,9 @@
     queue.add(new MarkReachableSuperAction(method, from));
   }
 
-  public void enqueueMarkReachableFieldAction(
+  public void enqueueMarkInstanceFieldAsReachableAction(
       ProgramField field, ProgramDefinition context, KeepReason reason) {
-    queue.add(new MarkReachableFieldAction(field, context, reason));
+    queue.add(new MarkInstanceFieldAsReachableAction(field, context, reason));
   }
 
   // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
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..58315d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -0,0 +1,427 @@
+// 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.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.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.Collections;
+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(
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          false);
+
+  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 method roots are the methods visited from an initial main dex root set. The set is
+  // cleared after the mergers has run.
+  private Set<DexMethod> tracedMethodRoots;
+  // 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;
+  // Bit indicating if the traced methods are cleared.
+  private boolean tracedMethodRootsCleared = false;
+
+  private MainDexInfo(Set<DexType> classList) {
+    this(
+        classList,
+        Collections.emptySet(),
+        Collections.emptySet(),
+        Collections.emptySet(),
+        /* synthesized classes - cannot be emptyset */ Sets.newIdentityHashSet(),
+        false);
+  }
+
+  private MainDexInfo(
+      Set<DexType> classList,
+      Set<DexType> tracedRoots,
+      Set<DexMethod> tracedMethodRoots,
+      Set<DexType> tracedDependencies,
+      Set<DexType> synthesizedClasses,
+      boolean tracedMethodRootsCleared) {
+    this.classList = classList;
+    this.tracedRoots = tracedRoots;
+    this.tracedMethodRoots = tracedMethodRoots;
+    this.tracedDependencies = tracedDependencies;
+    this.synthesizedClassesMap = new ConcurrentHashMap<>();
+    this.tracedMethodRootsCleared = tracedMethodRootsCleared;
+    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());
+  }
+
+  public boolean isTracedMethodRoot(DexMethod method) {
+    assert !tracedMethodRootsCleared : "Traced method roots are cleared after mergers has run";
+    return tracedMethodRoots.contains(method);
+  }
+
+  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 isTracedMethodRootsCleared() {
+    return tracedMethodRootsCleared;
+  }
+
+  public void clearTracedMethodRoots() {
+    this.tracedMethodRootsCleared = true;
+    this.tracedMethodRoots = Sets.newIdentityHashSet();
+  }
+
+  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));
+    // TODO(b/169927809): Methods could be pruned without the holder being pruned, however, one has
+    //  to have a reference for querying a root.
+    tracedMethodRoots.forEach(
+        method ->
+            ifNotRemoved(
+                method.getHolderType(), removedClasses, ignored -> builder.addRoot(method)));
+    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));
+    tracedMethodRoots.forEach(method -> builder.addRoot(lens.getRenamedMethodSignature(method)));
+    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 Builder builder() {
+    return new Builder(tracedMethodRootsCleared);
+  }
+
+  public static class Builder {
+
+    private final Set<DexType> list = Sets.newIdentityHashSet();
+    private final Set<DexType> roots = Sets.newIdentityHashSet();
+    private final Set<DexMethod> methodRoots = Sets.newIdentityHashSet();
+    private final Set<DexType> dependencies = Sets.newIdentityHashSet();
+    private final boolean tracedMethodRootsCleared;
+
+    private Builder(boolean tracedMethodRootsCleared) {
+      this.tracedMethodRootsCleared = tracedMethodRootsCleared;
+    }
+
+    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 addRoot(DexMethod method) {
+      methodRoots.add(method);
+    }
+
+    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),
+          methodRoots,
+          Sets.difference(dependencies, synthesizedClasses),
+          synthesizedClasses,
+          tracedMethodRootsCleared);
+    }
+
+    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/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index e296cd7..dee668f 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -4,15 +4,27 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
+
+import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDerivedContext;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
+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.IdentityHashMap;
+import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public class MissingClasses {
 
@@ -48,7 +60,7 @@
   public static class Builder {
 
     private final Set<DexType> alreadyMissingClasses;
-    private final Set<DexType> newMissingClasses = Sets.newIdentityHashSet();
+    private final Map<DexType, Set<DexReference>> newMissingClasses = new IdentityHashMap<>();
 
     // Set of missing types that are not to be reported as missing. This does not hide reports
     // if the same type is in newMissingClasses in which case it is reported regardless.
@@ -62,12 +74,27 @@
       this.alreadyMissingClasses = alreadyMissingClasses;
     }
 
-    public void addNewMissingClass(DexType type) {
-      newMissingClasses.add(type);
+    public void addNewMissingClass(DexType type, ProgramDerivedContext context) {
+      assert context != null;
+      assert context.getContext().getContextType() != type;
+      if (!alreadyMissingClasses.contains(type)) {
+        newMissingClasses
+            .computeIfAbsent(type, ignore -> Sets.newIdentityHashSet())
+            .add(context.getContext().getReference());
+      }
     }
 
-    public Builder addNewMissingClasses(Collection<DexType> types) {
-      newMissingClasses.addAll(types);
+    public void legacyAddNewMissingClass(DexType type) {
+      if (!alreadyMissingClasses.contains(type)) {
+        // The legacy reporting is context insensitive, so therefore we use the missing classes
+        // themselves as contexts.
+        newMissingClasses.computeIfAbsent(type, ignore -> Sets.newIdentityHashSet()).add(type);
+      }
+    }
+
+    @Deprecated
+    public Builder legacyAddNewMissingClasses(Collection<DexType> types) {
+      types.forEach(this::legacyAddNewMissingClass);
       return this;
     }
 
@@ -76,7 +103,7 @@
     }
 
     public boolean contains(DexType type) {
-      return alreadyMissingClasses.contains(type) || newMissingClasses.contains(type);
+      return alreadyMissingClasses.contains(type) || newMissingClasses.containsKey(type);
     }
 
     Builder removeAlreadyMissingClasses(Iterable<DexType> types) {
@@ -93,15 +120,12 @@
 
     public MissingClasses reportMissingClasses(AppView<?> appView) {
       InternalOptions options = appView.options();
-      Set<DexType> newMissingClassesWithoutDontWarn =
-          appView.getDontWarnConfiguration().getNonMatches(newMissingClasses);
-      newMissingClassesWithoutDontWarn.removeAll(alreadyMissingClasses);
-      newMissingClassesWithoutDontWarn.removeAll(
-          getAllowedMissingClasses(appView.dexItemFactory()));
-      if (!newMissingClassesWithoutDontWarn.isEmpty()) {
+      Map<DexType, Set<DexReference>> missingClassesToBeReported =
+          getMissingClassesToBeReported(appView);
+      if (!missingClassesToBeReported.isEmpty()) {
         MissingClassesDiagnostic diagnostic =
             new MissingClassesDiagnostic.Builder()
-                .addMissingClasses(newMissingClassesWithoutDontWarn)
+                .addMissingClasses(missingClassesToBeReported)
                 .setFatal(!options.ignoreMissingClasses)
                 .build();
         if (options.ignoreMissingClasses) {
@@ -113,21 +137,83 @@
       return build();
     }
 
-    private static Collection<DexType> getAllowedMissingClasses(DexItemFactory dexItemFactory) {
-      return ImmutableList.of(
-          dexItemFactory.annotationDefault,
-          dexItemFactory.annotationMethodParameters,
-          dexItemFactory.annotationSourceDebugExtension,
-          dexItemFactory.annotationThrows,
-          // TODO(b/176133674) StringConcatFactory is backported, but the class is reported as
-          //  missing because the enqueuer runs prior to backporting and thus sees the non-desugared
-          //  code.
-          dexItemFactory.stringConcatFactoryType);
+    private Map<DexType, Set<DexReference>> getMissingClassesToBeReported(AppView<?> appView) {
+      Predicate<DexType> allowedMissingClassesPredicate =
+          getIsAllowedMissingClassesPredicate(appView);
+      Map<DexType, Set<DexReference>> missingClassesToBeReported =
+          new IdentityHashMap<>(newMissingClasses.size());
+      newMissingClasses.forEach(
+          (missingClass, contexts) -> {
+            // Don't report "allowed" missing classes (e.g., classes matched by -dontwarn).
+            if (allowedMissingClassesPredicate.test(missingClass)) {
+              return;
+            }
+
+            // Remove all contexts that are matched by a -dontwarn rule (a missing class should not
+            // be reported if it os only referenced from contexts that are matched by a -dontwarn).
+            contexts.removeIf(
+                context -> appView.getDontWarnConfiguration().matches(context.getContextType()));
+
+            // If there are any contexts not matched by a -dontwarn rule, then report.
+            if (!contexts.isEmpty()) {
+              missingClassesToBeReported.put(missingClass, contexts);
+            }
+          });
+      return missingClassesToBeReported;
     }
 
+    private static Predicate<DexType> getIsAllowedMissingClassesPredicate(AppView<?> appView) {
+      Set<DexType> allowedMissingClasses = getAllowedMissingClasses(appView.dexItemFactory());
+      Predicate<DexType> compilerSynthesizedAllowingMissingClassPredicate =
+          getIsCompilerSynthesizedAllowedMissingClassesPredicate(appView);
+      DontWarnConfiguration dontWarnConfiguration = appView.getDontWarnConfiguration();
+      return type ->
+          allowedMissingClasses.contains(type)
+              || compilerSynthesizedAllowingMissingClassPredicate.test(type)
+              || dontWarnConfiguration.matches(type);
+    }
+
+    private static Set<DexType> getAllowedMissingClasses(DexItemFactory dexItemFactory) {
+      return ImmutableSet.<DexType>builder()
+          .add(
+              dexItemFactory.annotationDefault,
+              dexItemFactory.annotationMethodParameters,
+              dexItemFactory.annotationSourceDebugExtension,
+              dexItemFactory.annotationSynthesizedClass,
+              dexItemFactory.annotationSynthesizedClassMap,
+              dexItemFactory.annotationThrows,
+              dexItemFactory.serializedLambdaType,
+              // TODO(b/176133674) StringConcatFactory is backported, but the class is reported as
+              //  missing because the enqueuer runs prior to backporting and thus sees the
+              //  non-desugared code.
+              dexItemFactory.stringConcatFactoryType)
+          .addAll(dexItemFactory.getConversionTypes())
+          .build();
+    }
+
+    private static Predicate<DexType> getIsCompilerSynthesizedAllowedMissingClassesPredicate(
+        AppView<?> appView) {
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      InternalOptions options = appView.options();
+      DexString emulatedLibraryClassNameSuffix =
+          dexItemFactory.createString(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
+      DexString retargetPackageAndClassPrefixDescriptor =
+          dexItemFactory.createString(
+              getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
+      DexString vivifiedClassNamePrefix = dexItemFactory.createString(DESCRIPTOR_VIVIFIED_PREFIX);
+      return type -> {
+        DexString descriptor = type.getDescriptor();
+        return descriptor.startsWith(retargetPackageAndClassPrefixDescriptor)
+            || descriptor.startsWith(vivifiedClassNamePrefix)
+            || descriptor.endsWith(emulatedLibraryClassNameSuffix);
+      };
+    }
+
+
+
     /** Intentionally private, use {@link Builder#reportMissingClasses(AppView)}. */
     private MissingClasses build() {
-      // Extend the newMissingClasses set with all other missing classes.
+      // Return the new set of missing classes.
       //
       // We also add newIgnoredMissingClasses to newMissingClasses to be able to assert that we have
       // a closed world after the first round of tree shaking: we should never lookup a class that
@@ -137,9 +223,9 @@
       // Note: At this point, all missing classes in newMissingClasses have already been reported.
       // Thus adding newIgnoredMissingClasses to newMissingClasses will not lead to reports for the
       // classes in newIgnoredMissingClasses.
-      newMissingClasses.addAll(alreadyMissingClasses);
-      newMissingClasses.addAll(newIgnoredMissingClasses);
-      return new MissingClasses(newMissingClasses);
+      return new MissingClasses(
+          SetUtils.newIdentityHashSet(
+              alreadyMissingClasses, newMissingClasses.keySet(), newIgnoredMissingClasses));
     }
 
     public boolean wasAlreadyMissing(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
index 5965cc5..0f4e97b 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
@@ -4,34 +4,130 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.google.common.collect.ImmutableSortedSet;
-import java.util.Collection;
+import com.android.tools.r8.utils.FieldReferenceUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
-import java.util.SortedSet;
+import java.util.SortedMap;
+import java.util.function.Function;
 
 @Keep
 public class MissingClassesDiagnostic implements Diagnostic {
 
-  private final boolean fatal;
-  private final SortedSet<ClassReference> missingClasses;
+  private static class MissingClassAccessContexts {
 
-  private MissingClassesDiagnostic(boolean fatal, SortedSet<ClassReference> missingClasses) {
+    private ImmutableSet<ClassReference> classContexts;
+    private ImmutableSet<FieldReference> fieldContexts;
+    private ImmutableSet<MethodReference> methodContexts;
+
+    private MissingClassAccessContexts(
+        ImmutableSet<ClassReference> classContexts,
+        ImmutableSet<FieldReference> fieldContexts,
+        ImmutableSet<MethodReference> methodContexts) {
+      this.classContexts = classContexts;
+      this.fieldContexts = fieldContexts;
+      this.methodContexts = methodContexts;
+    }
+
+    static Builder builder() {
+      return new Builder();
+    }
+
+    String getReferencedFromMessageSuffix(ClassReference missingClass) {
+      if (!fieldContexts.isEmpty()) {
+        return " (referenced from: "
+            + FieldReferenceUtils.toSourceString(fieldContexts.iterator().next())
+            + ")";
+      }
+      if (!methodContexts.isEmpty()) {
+        return " (referenced from: "
+            + MethodReferenceUtils.toSourceString(methodContexts.iterator().next())
+            + ")";
+      }
+      // TODO(b/175543745): The legacy reporting is context insensitive, and therefore uses the
+      //  missing classes as their own context. Once legacy reporting is removed, this should be
+      //  simplified to taking the first context.
+      Optional<ClassReference> classContext =
+          classContexts.stream().filter(not(missingClass::equals)).findFirst();
+      return classContext
+          .map(classReference -> " (referenced from: " + classReference.getTypeName() + ")")
+          .orElse("");
+    }
+
+    static class Builder {
+
+      private final Set<DexReference> contexts = Sets.newIdentityHashSet();
+
+      Builder addAll(Set<DexReference> contexts) {
+        this.contexts.addAll(contexts);
+        return this;
+      }
+
+      // TODO(b/179249745): Sort on demand in getReferencedFromMessageSuffix() instead.
+      MissingClassAccessContexts build() {
+        // Sort the contexts for deterministic reporting.
+        List<DexType> classContexts = new ArrayList<>();
+        List<DexField> fieldContexts = new ArrayList<>();
+        List<DexMethod> methodContexts = new ArrayList<>();
+        contexts.forEach(
+            context -> context.apply(classContexts::add, fieldContexts::add, methodContexts::add));
+        Collections.sort(classContexts);
+        Collections.sort(fieldContexts);
+        Collections.sort(methodContexts);
+
+        // Build immutable sets (which preserve insertion order) from the sorted lists, mapping each
+        // DexType, DexField, and DexMethod to ClassReference, FieldReference, and MethodReference,
+        // respectively.
+        return new MissingClassAccessContexts(
+            toImmutableSet(classContexts, DexType::asClassReference),
+            toImmutableSet(fieldContexts, DexField::asFieldReference),
+            toImmutableSet(methodContexts, DexMethod::asMethodReference));
+      }
+
+      private <S, T> ImmutableSet<T> toImmutableSet(List<S> list, Function<S, T> fn) {
+        ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+        list.forEach(element -> builder.add(fn.apply(element)));
+        return builder.build();
+      }
+    }
+  }
+
+  private final boolean fatal;
+  private final SortedMap<ClassReference, MissingClassAccessContexts> missingClasses;
+
+  private MissingClassesDiagnostic(
+      boolean fatal, SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
     assert !missingClasses.isEmpty();
     this.fatal = fatal;
     this.missingClasses = missingClasses;
   }
 
   public Set<ClassReference> getMissingClasses() {
-    return missingClasses;
+    return missingClasses.keySet();
   }
 
   /** A missing class(es) failure can generally not be attributed to a single origin. */
@@ -46,7 +142,6 @@
     return Position.UNKNOWN;
   }
 
-  // TODO(b/175755807): Extend diagnostic message with contextual information.
   @Override
   public String getDiagnosticMessage() {
     return fatal ? getFatalDiagnosticMessage() : getNonFatalDiagnosticMessage();
@@ -54,43 +149,70 @@
 
   private String getFatalDiagnosticMessage() {
     if (missingClasses.size() == 1) {
-      return "Compilation can't be completed because the class "
-          + missingClasses.iterator().next().getTypeName()
-          + " is missing.";
+      StringBuilder builder =
+          new StringBuilder(
+              "Compilation can't be completed because the following class is missing: ");
+      writeMissingClass(builder, missingClasses.entrySet().iterator().next());
+      return builder.append(".").toString();
     }
+
     StringBuilder builder =
         new StringBuilder("Compilation can't be completed because the following ")
             .append(missingClasses.size())
             .append(" classes are missing:");
-    for (ClassReference missingClass : missingClasses) {
-      builder.append(System.lineSeparator()).append("- ").append(missingClass.getTypeName());
-    }
+    missingClasses.forEach(
+        (missingClass, contexts) ->
+            writeMissingClass(
+                builder.append(System.lineSeparator()).append("- "), missingClass, contexts));
     return builder.toString();
   }
 
   private String getNonFatalDiagnosticMessage() {
     StringBuilder builder = new StringBuilder();
-    Iterator<ClassReference> missingClassesIterator = missingClasses.iterator();
-    while (missingClassesIterator.hasNext()) {
-      ClassReference missingClass = missingClassesIterator.next();
-      builder.append("Missing class ").append(missingClass.getTypeName());
-      if (missingClassesIterator.hasNext()) {
-        builder.append(System.lineSeparator());
-      }
-    }
+    Iterator<Entry<ClassReference, MissingClassAccessContexts>> missingClassesIterator =
+        missingClasses.entrySet().iterator();
+
+    // The diagnostic is always non-empty.
+    assert missingClassesIterator.hasNext();
+
+    // Write first line.
+    writeMissingClass(builder.append("Missing class "), missingClassesIterator.next());
+
+    // Write remaining lines with line separator before.
+    missingClassesIterator.forEachRemaining(
+        missingClassInfo ->
+            writeMissingClass(
+                builder.append(System.lineSeparator()).append("Missing class "), missingClassInfo));
+
     return builder.toString();
   }
 
+  private static void writeMissingClass(
+      StringBuilder builder, Entry<ClassReference, MissingClassAccessContexts> missingClassInfo) {
+    writeMissingClass(builder, missingClassInfo.getKey(), missingClassInfo.getValue());
+  }
+
+  private static void writeMissingClass(
+      StringBuilder builder, ClassReference missingClass, MissingClassAccessContexts contexts) {
+    builder
+        .append(missingClass.getTypeName())
+        .append(contexts.getReferencedFromMessageSuffix(missingClass));
+  }
+
   public static class Builder {
 
     private boolean fatal;
-    private ImmutableSortedSet.Builder<ClassReference> missingClassesBuilder =
-        ImmutableSortedSet.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
+    private ImmutableSortedMap.Builder<ClassReference, MissingClassAccessContexts>
+        missingClassesBuilder =
+            ImmutableSortedMap.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
 
-    public MissingClassesDiagnostic.Builder addMissingClasses(Collection<DexType> missingClasses) {
-      for (DexType missingClass : missingClasses) {
-        missingClassesBuilder.add(Reference.classFromDescriptor(missingClass.toDescriptorString()));
-      }
+    public MissingClassesDiagnostic.Builder addMissingClasses(
+        Map<DexType, Set<DexReference>> missingClasses) {
+      missingClasses.forEach(
+          (missingClass, contexts) ->
+              missingClassesBuilder.put(
+                  Reference.classFromDescriptor(missingClass.toDescriptorString()),
+                  MissingClassAccessContexts.builder().addAll(contexts).build()));
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index aac168d..fbedd8e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -22,6 +24,9 @@
 public abstract class ProguardConfigurationRule extends ProguardClassSpecification {
 
   private boolean used = false;
+  // TODO(b/164019179): Since we are using the rule language for tracing main dex we can end up in
+  //  a situation where the references to types are dead.
+  private boolean canReferenceDeadTypes = false;
 
   ProguardConfigurationRule(
       Origin origin,
@@ -101,13 +106,32 @@
     return null;
   }
 
+  public void canReferenceDeadTypes() {
+    this.canReferenceDeadTypes = true;
+  }
+
   Iterable<DexProgramClass> relevantCandidatesForRule(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       Iterable<DexProgramClass> defaultValue) {
     List<DexType> specificTypes = getClassNames().asSpecificDexTypes();
     if (specificTypes != null) {
-      return DexProgramClass.asProgramClasses(specificTypes, appView);
+      return DexProgramClass.asProgramClasses(
+          specificTypes,
+          new DexDefinitionSupplier() {
+            @Override
+            public DexClass definitionFor(DexType type) {
+              if (canReferenceDeadTypes) {
+                return appView.appInfo().definitionForWithoutExistenceAssert(type);
+              }
+              return appView.definitionFor(type);
+            }
+
+            @Override
+            public DexItemFactory dexItemFactory() {
+              return appView.dexItemFactory();
+            }
+          });
     }
     if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
       DexType type = getInheritanceClassName().getSpecificType();
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 f4567e9..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;
@@ -30,6 +31,7 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
@@ -1765,6 +1767,37 @@
     public int size() {
       return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
     }
+
+    public void forEachReference(
+        BiConsumer<? super DexReference, Set<ProguardKeepRuleBase>> consumer) {
+      forEachClass(consumer);
+      forEachMember(consumer);
+    }
+
+    private MutableItemsWithRules prune(Set<DexType> prunedClasses) {
+      MutableItemsWithRules prunedItemsWithRules = new MutableItemsWithRules();
+      forEachReference(
+          (reference, rules) -> {
+            if (!prunedClasses.contains(reference.getContextType())) {
+              prunedItemsWithRules.addReferenceWithRules(reference, rules);
+            }
+          });
+      return prunedItemsWithRules;
+    }
+
+    private MutableItemsWithRules rewrittenWithLens(GraphLens graphLens) {
+      if (graphLens.isIdentityLens()) {
+        return this;
+      }
+      MutableItemsWithRules rewrittenItemsWithRules = new MutableItemsWithRules();
+      forEachReference(
+          (reference, rules) ->
+              rewriteAndApplyIfNotPrimitiveType(
+                  graphLens,
+                  reference,
+                  rewritten -> rewrittenItemsWithRules.addReferenceWithRules(rewritten, rules)));
+      return rewrittenItemsWithRules;
+    }
   }
 
   public static class RootSet extends RootSetBase {
@@ -1889,7 +1922,7 @@
     }
 
     // Add dependent items that depend on -if rules.
-    private static void addDependentItems(
+    static void addDependentItems(
         Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd,
         Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) {
       dependentItemsToAdd.forEach(
@@ -2193,4 +2226,165 @@
       return new ConsequentRootSetBuilder(appView, subtypingInfo, enqueuer);
     }
   }
+
+  public static class MainDexRootSetBuilder extends RootSetBuilder {
+
+    private MainDexRootSetBuilder(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        SubtypingInfo subtypingInfo,
+        Iterable<? extends ProguardConfigurationRule> rules) {
+      super(appView, subtypingInfo, rules);
+    }
+
+    @Override
+    public MainDexRootSet build(ExecutorService executorService) throws ExecutionException {
+      // Call the super builder to have if-tests calculated automatically.
+      RootSet rootSet = super.build(executorService);
+      return new MainDexRootSet(
+          rootSet.noShrinking,
+          rootSet.reasonAsked,
+          rootSet.checkDiscarded,
+          rootSet.dependentNoShrinking,
+          rootSet.ifRules,
+          rootSet.delayedRootSetActionItems);
+    }
+  }
+
+  public static class MainDexRootSet extends RootSet {
+
+    public MainDexRootSet(
+        MutableItemsWithRules noShrinking,
+        ImmutableList<DexReference> reasonAsked,
+        ImmutableList<DexReference> checkDiscarded,
+        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+        Set<ProguardIfRule> ifRules,
+        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+      super(
+          noShrinking,
+          new MutableItemsWithRules(),
+          Collections.emptySet(),
+          reasonAsked,
+          checkDiscarded,
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          PredicateSet.empty(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptySet(),
+          Collections.emptyMap(),
+          Collections.emptyMap(),
+          Collections.emptyMap(),
+          dependentNoShrinking,
+          Collections.emptyMap(),
+          Collections.emptyMap(),
+          Collections.emptySet(),
+          ifRules,
+          delayedRootSetActionItems);
+    }
+
+    @Override
+    void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
+      if (addNoShrinking) {
+        noShrinking.addAll(consequentRootSet.noShrinking);
+      }
+      addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
+      consequentRootSet.dependentKeepClassCompatRule.forEach(
+          (type, rules) ->
+              dependentKeepClassCompatRule
+                  .computeIfAbsent(type, k -> new HashSet<>())
+                  .addAll(rules));
+    }
+
+    public static MainDexRootSetBuilder builder(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        SubtypingInfo subtypingInfo,
+        Iterable<? extends ProguardConfigurationRule> rules) {
+      return new MainDexRootSetBuilder(appView, subtypingInfo, rules);
+    }
+
+    @Override
+    void shouldNotBeMinified(DexReference reference) {
+      // Do nothing.
+    }
+
+    public MainDexRootSet rewrittenWithLens(GraphLens graphLens) {
+      if (graphLens.isIdentityLens()) {
+        return this;
+      }
+      Map<DexReference, MutableItemsWithRules> rewrittenDependent = new IdentityHashMap<>();
+      dependentNoShrinking.forEach(
+          (reference, rules) -> {
+            // Rewriting a reference can result in us having to merge items with rules.
+            rewriteAndApplyIfNotPrimitiveType(
+                graphLens,
+                reference,
+                rewritten -> {
+                  MutableItemsWithRules rewrittenRules =
+                      rewrittenDependent.computeIfAbsent(
+                          graphLens.lookupReference(reference),
+                          rewrittenRef -> new MutableItemsWithRules());
+                  rewrittenRules.addAll(rules.rewrittenWithLens(graphLens));
+                });
+          });
+
+      ImmutableList.Builder<DexReference> rewrittenCheckDiscarded = ImmutableList.builder();
+      checkDiscarded.forEach(
+          reference ->
+              rewriteAndApplyIfNotPrimitiveType(
+                  graphLens, reference, rewrittenCheckDiscarded::add));
+      ImmutableList.Builder<DexReference> rewrittenReasonAsked = ImmutableList.builder();
+      reasonAsked.forEach(
+          reference ->
+              rewriteAndApplyIfNotPrimitiveType(graphLens, reference, rewrittenReasonAsked::add));
+      // TODO(b/164019179): If rules can now reference dead items. These should be pruned or
+      //  rewritten
+      ifRules.forEach(ProguardIfRule::canReferenceDeadTypes);
+      // All delayed root set actions should have been processed at this point.
+      assert delayedRootSetActionItems.isEmpty();
+      return new MainDexRootSet(
+          noShrinking.rewrittenWithLens(graphLens),
+          rewrittenReasonAsked.build(),
+          rewrittenCheckDiscarded.build(),
+          rewrittenDependent,
+          ifRules,
+          delayedRootSetActionItems);
+    }
+
+    public MainDexRootSet withoutPrunedItems(PrunedItems prunedItems) {
+      if (prunedItems.isEmpty()) {
+        return this;
+      }
+      Map<DexReference, MutableItemsWithRules> prunedDependent = new IdentityHashMap<>();
+      dependentNoShrinking.forEach(
+          (ref, rules) -> {
+            if (prunedItems.getRemovedClasses().contains(ref.getContextType())) {
+              // The dependent reference has been pruned and cannot lead to any additional items
+              return;
+            }
+            prunedDependent.put(ref, rules.prune(prunedItems.getRemovedClasses()));
+          });
+      // TODO(b/164019179): If rules can now reference dead items. These should be pruned or
+      //  rewritten
+      ifRules.forEach(ProguardIfRule::canReferenceDeadTypes);
+      // All delayed root set actions should have been processed at this point.
+      assert delayedRootSetActionItems.isEmpty();
+      return new MainDexRootSet(
+          noShrinking.prune(prunedItems.getRemovedClasses()),
+          reasonAsked,
+          checkDiscarded,
+          prunedDependent,
+          ifRules,
+          delayedRootSetActionItems);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 9155165..9b7a66b 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -376,7 +376,9 @@
 
   private boolean verifyNoDeadFields(DexProgramClass clazz) {
     for (DexEncodedField field : clazz.fields()) {
-      assert !field.getOptimizationInfo().isDead()
+      // Pinned field which type is never instantiated are always null, they are marked as dead
+      // so their reads and writes are cleared, but they are still in the program.
+      assert !field.getOptimizationInfo().isDead() || appView.appInfo().isPinned(field)
           : "Expected field `" + field.field.toSourceString() + "` to be absent";
     }
     return true;
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 c52fd0f..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 932359d..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,8 +14,9 @@
 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;
 import java.util.Set;
 
@@ -63,14 +64,27 @@
   }
 
   static SynthesizingContext fromSyntheticContextChange(
-      DexType syntheticType, SynthesizingContext oldContext, DexItemFactory factory) {
+      SyntheticKind kind,
+      DexType syntheticType,
+      SynthesizingContext oldContext,
+      DexItemFactory factory) {
     String descriptor = syntheticType.toDescriptorString();
-    int i = descriptor.indexOf(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
-    if (i <= 0) {
-      assert false : "Unexpected synthetic without internal separator: " + syntheticType;
-      return null;
+    DexType newContext;
+    if (kind.isFixedSuffixSynthetic) {
+      int i = descriptor.lastIndexOf(kind.descriptor);
+      if (i < 0 || descriptor.length() != i + kind.descriptor.length() + 1) {
+        assert false : "Unexpected fixed synthetic with invalid suffix: " + syntheticType;
+        return null;
+      }
+      newContext = factory.createType(descriptor.substring(0, i) + ";");
+    } else {
+      int i = descriptor.indexOf(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
+      if (i <= 0) {
+        assert false : "Unexpected synthetic without internal separator: " + syntheticType;
+        return null;
+      }
+      newContext = factory.createType(descriptor.substring(0, i) + ";");
     }
-    DexType newContext = factory.createType(descriptor.substring(0, i) + ";");
     return newContext == oldContext.getSynthesizingContextType()
         ? oldContext
         : new SynthesizingContext(newContext, newContext, oldContext.inputContextOrigin);
@@ -141,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/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index eefe1b9..239e7d0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -64,7 +64,7 @@
   final HashCode computeHash(
       RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
     Hasher hasher = Hashing.murmur3_128().newHasher();
-    if (intermediate) {
+    if (intermediate || getKind().isFixedSuffixSynthetic) {
       // If in intermediate mode, include the context type as sharing is restricted to within a
       // single context.
       getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
@@ -95,7 +95,7 @@
       boolean includeContext,
       GraphLens graphLens,
       ClassToFeatureSplitMap classToFeatureSplitMap) {
-    if (includeContext) {
+    if (includeContext || getKind().isFixedSuffixSynthetic) {
       int order = getContext().compareTo(other.getContext());
       if (order != 0) {
         return order;
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 e2e0b55..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) {
@@ -765,16 +764,17 @@
       DexType representativeContext,
       Map<DexType, NumberGenerator> generators,
       AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
+    if (kind.isFixedSuffixSynthetic) {
+      return SyntheticNaming.createExternalType(kind, representativeContext, "", factory);
+    }
     NumberGenerator generator =
         generators.computeIfAbsent(representativeContext, k -> new NumberGenerator());
     DexType externalType;
     do {
       externalType =
           SyntheticNaming.createExternalType(
-              kind,
-              representativeContext,
-              Integer.toString(generator.next()),
-              appView.dexItemFactory());
+              kind, representativeContext, Integer.toString(generator.next()), factory);
       DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(externalType);
       if (clazz != null && isNotSyntheticType(clazz.type)) {
         assert options.testing.allowConflictingSyntheticTypes
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 c198197..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.
@@ -305,6 +305,36 @@
     return clazz;
   }
 
+  public DexProgramClass createFixedClass(
+      SyntheticKind kind,
+      DexProgramClass context,
+      DexItemFactory factory,
+      Consumer<SyntheticProgramClassBuilder> fn) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = getSynthesizingContext(context);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+    SyntheticProgramClassBuilder classBuilder =
+        new SyntheticProgramClassBuilder(type, outerContext, factory);
+    fn.accept(classBuilder);
+    DexProgramClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
+  public DexClasspathClass createFixedClasspathClass(
+      SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+    SyntheticClasspathClassBuilder classBuilder =
+        new SyntheticClasspathClassBuilder(type, outerContext, factory);
+    DexClasspathClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
       SyntheticKind kind,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 09a0f1a..0b53962 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -66,7 +66,7 @@
     if (method != rewritten) {
       context =
           SynthesizingContext.fromSyntheticContextChange(
-              rewritten.holder, context, lens.dexItemFactory());
+              getKind(), rewritten.holder, context, lens.dexItemFactory());
       if (context == null) {
         return null;
       }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index fc5b278..c433ec0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -24,6 +24,7 @@
     // Class synthetics.
     COMPANION_CLASS("CompanionClass", false),
     LAMBDA("Lambda", false),
+    INIT_TYPE_ARGUMENT("-IA", false, true),
     // Method synthetics.
     BACKPORT("Backport", true),
     STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
@@ -37,10 +38,17 @@
 
     public final String descriptor;
     public final boolean isSingleSyntheticMethod;
+    public final boolean isFixedSuffixSynthetic;
 
     SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) {
+      this(descriptor, isSingleSyntheticMethod, false);
+    }
+
+    SyntheticKind(
+        String descriptor, boolean isSingleSyntheticMethod, boolean isFixedSuffixSynthetic) {
       this.descriptor = descriptor;
       this.isSingleSyntheticMethod = isSingleSyntheticMethod;
+      this.isFixedSuffixSynthetic = isFixedSuffixSynthetic;
     }
 
     public static SyntheticKind fromDescriptor(String descriptor) {
@@ -73,8 +81,15 @@
         || typeName.contains(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR);
   }
 
+  public static DexType createFixedType(
+      SyntheticKind kind, SynthesizingContext context, DexItemFactory factory) {
+    assert kind.isFixedSuffixSynthetic;
+    return createType("", kind, context.getSynthesizingContextType(), "", factory);
+  }
+
   static DexType createInternalType(
       SyntheticKind kind, SynthesizingContext context, String id, DexItemFactory factory) {
+    assert !kind.isFixedSuffixSynthetic;
     return createType(
         INTERNAL_SYNTHETIC_CLASS_SEPARATOR,
         kind,
@@ -85,7 +100,13 @@
 
   static DexType createExternalType(
       SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
-    return createType(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context, id, factory);
+    assert kind.isFixedSuffixSynthetic == id.isEmpty();
+    return createType(
+        kind.isFixedSuffixSynthetic ? "" : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR,
+        kind,
+        context,
+        id,
+        factory);
   }
 
   private static DexType createType(
@@ -134,6 +155,10 @@
 
   static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) {
     String typeName = clazz.getTypeName();
+    if (kind.isFixedSuffixSynthetic) {
+      assert phase == null;
+      return clazz.getBinaryName().endsWith(kind.descriptor);
+    }
     String separator = getPhaseSeparator(phase);
     int i = typeName.indexOf(separator);
     return i >= 0 && checkMatchFrom(kind, typeName, i, separator, phase == Phase.EXTERNAL);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
index 8b42eb6..3506dab 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
@@ -48,7 +48,9 @@
 
   @Override
   public boolean isValid() {
-    return clazz.isPublic() && clazz.isFinal() && clazz.accessFlags.isSynthetic();
+    return clazz.isPublic()
+        && clazz.accessFlags.isSynthetic()
+        && (clazz.isFinal() || clazz.isAbstract());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
index 3472268..7e798e0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -53,7 +53,8 @@
     // Ensure that if a synthetic moves its context moves consistently.
     if (type != rewritten) {
       context =
-          SynthesizingContext.fromSyntheticContextChange(rewritten, context, lens.dexItemFactory());
+          SynthesizingContext.fromSyntheticContextChange(
+              getKind(), rewritten, context, lens.dexItemFactory());
       if (context == null) {
         return null;
       }
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..039f28d 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.none());
   }
 
   void run(TraceReferencesConsumer consumer) {
diff --git a/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java
new file mode 100644
index 0000000..cb26884
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FieldReferenceUtils.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.references.FieldReference;
+
+public class FieldReferenceUtils {
+
+  public static String toSourceString(FieldReference fieldReference) {
+    return fieldReference.getFieldType().getTypeName()
+        + " "
+        + fieldReference.getHolderClass().getTypeName()
+        + "."
+        + fieldReference.getFieldName();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 0d4a768..118a5b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -40,7 +40,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
@@ -50,7 +49,6 @@
 import com.android.tools.r8.ir.desugar.nest.Nest;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
-import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.Reference;
@@ -201,7 +199,6 @@
     enableClassInlining = false;
     enableClassStaticizer = false;
     enableDevirtualization = false;
-    enableLambdaMerging = false;
     horizontalClassMergerOptions.disable();
     enableVerticalClassMerging = false;
     enableEnumUnboxing = false;
@@ -488,6 +485,11 @@
     return !canUseNestBasedAccess();
   }
 
+  public boolean canUseRecords() {
+    // TODO(b/169645628): Replace by true when records are supported.
+    return testing.canUseRecords;
+  }
+
   public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
   public Set<String> extensiveInterfaceMethodMinifierLoggingFilter =
       getExtensiveInterfaceMethodMinifierLoggingFilter();
@@ -502,8 +504,6 @@
 
   // Flag to turn on/offLoad/store optimization in the Cf back-end.
   public boolean enableLoadStoreOptimization = true;
-  // Flag to turn on/off lambda class merging in R8.
-  public boolean enableLambdaMerging = false;
   // Flag to turn on/off desugaring in D8/R8.
   public DesugarState desugarState = DesugarState.ON;
   // Flag to turn on/off reduction of nest to improve class merging optimizations.
@@ -600,6 +600,8 @@
   public boolean printCfg = false;
   public String printCfgFile;
   public boolean ignoreMissingClasses = false;
+  public boolean reportMissingClassesInEnclosingMethodAttribute = false;
+  public boolean reportMissingClassesInInnerClassAttributes = false;
 
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
@@ -1139,20 +1141,17 @@
     public boolean enableConstructorMerging = true;
     // TODO(b/174809311): Update or remove the option and its tests after new lambdas synthetics.
     public boolean enableJavaLambdaMerging = false;
-    public boolean enableKotlinLambdaMerging = true;
 
     public int syntheticArgumentCount = 3;
     public int maxGroupSize = 30;
 
+    // TODO(b/179019716): Add support for merging in presence of annotations.
+    public boolean skipNoClassesOrMembersWithAnnotationsPolicyForTesting = false;
+
     public void disable() {
       enable = false;
     }
 
-    @Deprecated
-    public void disableKotlinLambdaMerging() {
-      enableKotlinLambdaMerging = false;
-    }
-
     public void enable() {
       enable = true;
     }
@@ -1165,10 +1164,6 @@
       enableJavaLambdaMerging = true;
     }
 
-    public void enableKotlinLambdaMergingIf(boolean enableKotlinLambdaMerging) {
-      this.enableKotlinLambdaMerging = enableKotlinLambdaMerging;
-    }
-
     public int getMaxGroupSize() {
       return maxGroupSize;
     }
@@ -1192,10 +1187,6 @@
     public boolean isJavaLambdaMergingEnabled() {
       return enableJavaLambdaMerging;
     }
-
-    public boolean isKotlinLambdaMergingEnabled() {
-      return enableKotlinLambdaMerging;
-    }
   }
 
   public static class ProtoShrinkingOptions {
@@ -1243,9 +1234,6 @@
 
     public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
 
-    public Function<DexProgramClass, KotlinLambdaGroupIdFactory> kotlinLambdaMergerFactoryForClass =
-        KotlinLambdaGroupIdFactory::getFactoryForClass;
-
     public BiConsumer<ProgramMethod, MethodProcessingId> methodProcessingIdConsumer = null;
 
     public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration>
@@ -1257,9 +1245,6 @@
     public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
         ConsumerUtils.emptyBiConsumer();
 
-    public BiConsumer<DexItemFactory, HorizontallyMergedLambdaClasses>
-        horizontallyMergedLambdaClassesConsumer = ConsumerUtils.emptyBiConsumer();
-
     public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer =
         ConsumerUtils.emptyBiConsumer();
 
@@ -1303,6 +1288,7 @@
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
+    public boolean canUseRecords = false;
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
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/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 7f48f2a..e76f21a 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -4,21 +4,39 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.references.ArrayReference;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
 
 public class MethodReferenceUtils {
 
+  public static MethodReference mainMethod(ClassReference type) {
+    ArrayReference stringArrayType = Reference.array(Reference.classFromClass(String.class), 1);
+    return Reference.method(type, "main", ImmutableList.of(stringArrayType), null);
+  }
+
   public static String toSourceStringWithoutHolderAndReturnType(MethodReference methodReference) {
     return toSourceString(methodReference, false, false);
   }
 
+  public static String toSourceString(MethodReference methodReference) {
+    return toSourceString(methodReference, true, true);
+  }
+
   public static String toSourceString(
       MethodReference methodReference, boolean includeHolder, boolean includeReturnType) {
     StringBuilder builder = new StringBuilder();
     if (includeReturnType) {
-      builder.append(methodReference.getReturnType().getTypeName()).append(" ");
+      builder
+          .append(
+              methodReference.getReturnType() != null
+                  ? methodReference.getReturnType().getTypeName()
+                  : "void")
+          .append(" ");
     }
     if (includeHolder) {
       builder.append(methodReference.getHolderClass().getTypeName()).append(".");
diff --git a/src/main/java/com/android/tools/r8/utils/PredicateSet.java b/src/main/java/com/android/tools/r8/utils/PredicateSet.java
index da15974..0eb186d 100644
--- a/src/main/java/com/android/tools/r8/utils/PredicateSet.java
+++ b/src/main/java/com/android/tools/r8/utils/PredicateSet.java
@@ -24,6 +24,10 @@
     predicates.add(predicate);
   }
 
+  public static <T> PredicateSet<T> empty() {
+    return new PredicateSet<>();
+  }
+
   public PredicateSet<T> rewriteItems(Function<T, T> mapping) {
     PredicateSet<T> set = new PredicateSet<>();
     for (T item : elements) {
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 c67225c..a278196 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -37,6 +37,14 @@
     return result;
   }
 
+  public static <T> Set<T> newIdentityHashSet(Iterable<T> c1, Iterable<T> c2, Iterable<T> c3) {
+    Set<T> result = Sets.newIdentityHashSet();
+    c1.forEach(result::add);
+    c2.forEach(result::add);
+    c3.forEach(result::add);
+    return result;
+  }
+
   public static <T> Set<T> newIdentityHashSet(int capacity) {
     return Collections.newSetFromMap(new IdentityHashMap<>(capacity));
   }
@@ -54,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/examplesJava15/records/Main.java b/src/test/examplesJava15/records/EmptyRecord.java
similarity index 60%
copy from src/test/examplesJava15/records/Main.java
copy to src/test/examplesJava15/records/EmptyRecord.java
index 7be696d..5d16e44 100644
--- a/src/test/examplesJava15/records/Main.java
+++ b/src/test/examplesJava15/records/EmptyRecord.java
@@ -4,13 +4,11 @@
 
 package records;
 
-public class Main {
+public class EmptyRecord {
 
-  record Person(String name, int age) {}
+  record Empty() {}
 
   public static void main(String[] args) {
-    Person janeDoe = new Person("Jane Doe", 42);
-    System.out.println(janeDoe.name);
-    System.out.println(janeDoe.age);
+    System.out.println(new Empty());
   }
 }
diff --git a/src/test/examplesJava15/records/RecordInstanceOf.java b/src/test/examplesJava15/records/RecordInstanceOf.java
new file mode 100644
index 0000000..591192c
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordInstanceOf.java
@@ -0,0 +1,21 @@
+// 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 records;
+
+public class RecordInstanceOf {
+
+  record Empty() {}
+
+  record Person(String name, int age) {}
+
+  public static void main(String[] args) {
+    Empty empty = new Empty();
+    Person janeDoe = new Person("Jane Doe", 42);
+    Object o = new Object();
+    System.out.println(janeDoe instanceof java.lang.Record);
+    System.out.println(empty instanceof java.lang.Record);
+    System.out.println(o instanceof java.lang.Record);
+  }
+}
diff --git a/src/test/examplesJava15/records/RecordInvokeCustom.java b/src/test/examplesJava15/records/RecordInvokeCustom.java
new file mode 100644
index 0000000..c460f56
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordInvokeCustom.java
@@ -0,0 +1,48 @@
+// 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 records;
+
+public class RecordInvokeCustom {
+
+  record Empty() {}
+
+  record Person(String name, int age) {}
+
+  public static void main(String[] args) {
+    emptyTest();
+    equalityTest();
+    toStringTest();
+  }
+
+  private static void emptyTest() {
+    Empty empty1 = new Empty();
+    Empty empty2 = new Empty();
+    System.out.println(empty1.toString());
+    System.out.println(empty1.equals(empty2));
+    System.out.println(empty1.hashCode() == empty2.hashCode());
+    System.out.println(empty1.toString().equals(empty2.toString()));
+  }
+
+  private static void toStringTest() {
+    Person janeDoe = new Person("Jane Doe", 42);
+    System.out.println(janeDoe.toString());
+  }
+
+  private static void equalityTest() {
+    Person jane1 = new Person("Jane Doe", 42);
+    Person jane2 = new Person("Jane Doe", 42);
+    String nonIdenticalString = "Jane " + (System.currentTimeMillis() > 0 ? "Doe" : "Zan");
+    Person jane3 = new Person(nonIdenticalString, 42);
+    Person bob = new Person("Bob", 42);
+    Person youngJane = new Person("Jane Doe", 22);
+    System.out.println(jane1.equals(jane2));
+    System.out.println(jane1.toString().equals(jane2.toString()));
+    System.out.println(nonIdenticalString == "Jane Doe"); // false.
+    System.out.println(nonIdenticalString.equals("Jane Doe")); // true.
+    System.out.println(jane1.equals(jane3));
+    System.out.println(jane1.equals(bob));
+    System.out.println(jane1.equals(youngJane));
+  }
+}
diff --git a/src/test/examplesJava15/records/RecordReflection.java b/src/test/examplesJava15/records/RecordReflection.java
new file mode 100644
index 0000000..1b94a12
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordReflection.java
@@ -0,0 +1,29 @@
+// 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 records;
+
+import java.util.Arrays;
+
+public class RecordReflection {
+
+  record Empty(){}
+
+  record Person(String name, int age) {}
+
+  record PersonGeneric <S extends CharSequence>(S name, int age) {}
+
+  public static void main(String[] args) {
+    System.out.println(Empty.class.isRecord());
+    System.out.println(Arrays.toString(Empty.class.getRecordComponents()));
+    System.out.println(Person.class.isRecord());
+    System.out.println(Arrays.toString(Person.class.getRecordComponents()));
+    System.out.println(PersonGeneric.class.isRecord());
+    System.out.println(Arrays.toString(PersonGeneric.class.getRecordComponents()));
+    System.out.println(Arrays.toString(PersonGeneric.class.getTypeParameters()));
+    System.out.println(Object.class.isRecord());
+    System.out.println(Arrays.toString(Object.class.getRecordComponents()));
+  }
+
+}
diff --git a/src/test/examplesJava15/records/RecordWithMembers.java b/src/test/examplesJava15/records/RecordWithMembers.java
new file mode 100644
index 0000000..6de2b99
--- /dev/null
+++ b/src/test/examplesJava15/records/RecordWithMembers.java
@@ -0,0 +1,69 @@
+// 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 records;
+
+public class RecordWithMembers {
+
+
+  record PersonWithConstructors(String name, int age) {
+
+    public PersonWithConstructors(String name, int age) {
+      this.name = name + "X";
+      this.age = age;
+    }
+
+    public PersonWithConstructors(String name) {
+      this(name, -1);
+    }
+  }
+
+  record PersonWithMethods(String name, int age) {
+    public static void staticPrint() {
+      System.out.println("print");
+    }
+
+    @Override
+    public String toString() {
+      return name + age;
+    }
+  }
+
+  record PersonWithFields(String name, int age) {
+
+    // Extra instance fields are not allowed on records.
+    public static String globalName;
+
+  }
+
+  public static void main(String[] args) {
+    personWithConstructorTest();
+    personWithMethodsTest();
+    personWithFieldsTest();
+  }
+
+  private static void personWithConstructorTest() {
+    PersonWithConstructors bob = new PersonWithConstructors("Bob", 43);
+    System.out.println(bob.name);
+    System.out.println(bob.age);
+    System.out.println(bob.name());
+    System.out.println(bob.age());
+    PersonWithConstructors felix = new PersonWithConstructors("Felix");
+    System.out.println(felix.name);
+    System.out.println(felix.age);
+    System.out.println(felix.name());
+    System.out.println(felix.age());
+  }
+
+  private static void personWithMethodsTest() {
+    PersonWithMethods.staticPrint();
+    PersonWithMethods bob = new PersonWithMethods("Bob", 43);
+    System.out.println(bob.toString());
+  }
+
+  private static void personWithFieldsTest() {
+    PersonWithFields.globalName = "extra";
+    System.out.println(PersonWithFields.globalName);
+  }
+}
diff --git a/src/test/examplesJava15/records/Main.java b/src/test/examplesJava15/records/SimpleRecord.java
similarity index 80%
rename from src/test/examplesJava15/records/Main.java
rename to src/test/examplesJava15/records/SimpleRecord.java
index 7be696d..1f8fca4 100644
--- a/src/test/examplesJava15/records/Main.java
+++ b/src/test/examplesJava15/records/SimpleRecord.java
@@ -4,7 +4,7 @@
 
 package records;
 
-public class Main {
+public class SimpleRecord {
 
   record Person(String name, int age) {}
 
@@ -12,5 +12,7 @@
     Person janeDoe = new Person("Jane Doe", 42);
     System.out.println(janeDoe.name);
     System.out.println(janeDoe.age);
+    System.out.println(janeDoe.name());
+    System.out.println(janeDoe.age());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
index 609fad5..d018732 100644
--- a/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/IntermediateCfD8TestBuilder.java
@@ -29,8 +29,7 @@
   private IntermediateCfD8TestBuilder(TestState state, AndroidApiLevel apiLevel) {
     super(state);
     cf2cf = D8TestBuilder.create(state, Backend.CF).setMinApi(apiLevel);
-    cf2dex =
-        D8TestBuilder.create(state, Backend.DEX).setMinApi(apiLevel).setDisableDesugaring(true);
+    cf2dex = D8TestBuilder.create(state, Backend.DEX).disableDesugaring().setMinApi(apiLevel);
   }
 
   public IntermediateCfD8TestBuilder addOptionsModification(Consumer<InternalOptions> fn) {
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index f8dc222..38202a6 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -41,10 +41,12 @@
 
   protected final KotlinCompiler kotlinc;
   protected final KotlinTargetVersion targetVersion;
+  protected final KotlinTestParameters kotlinParameters;
 
-  protected KotlinTestBase(KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    this.targetVersion = targetVersion;
-    this.kotlinc = kotlinc;
+  protected KotlinTestBase(KotlinTestParameters kotlinParameters) {
+    this.targetVersion = kotlinParameters.getTargetVersion();
+    this.kotlinc = kotlinParameters.getCompiler();
+    this.kotlinParameters = kotlinParameters;
   }
 
   protected static List<Path> getKotlinFilesInTestPackage(Package pkg) throws IOException {
@@ -125,6 +127,11 @@
       return this;
     }
 
+    public Path getForConfiguration(KotlinTestParameters kotlinParameters) {
+      return getForConfiguration(
+          kotlinParameters.getCompiler(), kotlinParameters.getTargetVersion());
+    }
+
     public Path getForConfiguration(KotlinCompiler compiler, KotlinTargetVersion targetVersion) {
       Map<KotlinTargetVersion, Path> kotlinTargetVersionPathMap = compiledPaths.get(compiler);
       if (kotlinTargetVersionPathMap == null) {
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
new file mode 100644
index 0000000..69a1cff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import java.util.ArrayList;
+import java.util.List;
+
+public class KotlinTestParameters {
+
+  private final int index;
+  private final KotlinCompiler kotlinc;
+  private final KotlinTargetVersion targetVersion;
+
+  private KotlinTestParameters(
+      KotlinCompiler kotlinc, KotlinTargetVersion targetVersion, int index) {
+    this.index = index;
+    this.kotlinc = kotlinc;
+    this.targetVersion = targetVersion;
+  }
+
+  public KotlinCompiler getCompiler() {
+    return kotlinc;
+  }
+
+  public KotlinTargetVersion getTargetVersion() {
+    return targetVersion;
+  }
+
+  public boolean isFirst() {
+    return index == 0;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public String toString() {
+    return kotlinc + "[target=" + targetVersion + "]";
+  }
+
+  public static class Builder {
+
+    private KotlinCompiler[] compilers;
+    private KotlinTargetVersion[] targetVersions;
+
+    private Builder() {}
+
+    public Builder withAllCompilers() {
+      compilers = ToolHelper.getKotlinCompilers();
+      return this;
+    }
+
+    public Builder withAllCompilersAndTargetVersions() {
+      return withAllCompilers().withAllTargetVersions();
+    }
+
+    public Builder withCompiler(KotlinCompiler compiler) {
+      compilers = new KotlinCompiler[] {compiler};
+      return this;
+    }
+
+    public Builder withAllTargetVersions() {
+      targetVersions = KotlinTargetVersion.values();
+      return this;
+    }
+
+    public Builder withTargetVersion(KotlinTargetVersion targetVersion) {
+      targetVersions = new KotlinTargetVersion[] {targetVersion};
+      return this;
+    }
+
+    public KotlinTestParametersCollection build() {
+      validate();
+      List<KotlinTestParameters> testParameters = new ArrayList<>();
+      int index = 0;
+      for (KotlinCompiler kotlinc : compilers) {
+        for (KotlinTargetVersion targetVersion : targetVersions) {
+          testParameters.add(new KotlinTestParameters(kotlinc, targetVersion, index++));
+        }
+      }
+      return new KotlinTestParametersCollection(testParameters);
+    }
+
+    private void validate() {
+      assertNotNull(compilers);
+      assertNotNull(targetVersions);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParametersCollection.java b/src/test/java/com/android/tools/r8/KotlinTestParametersCollection.java
new file mode 100644
index 0000000..6851116
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KotlinTestParametersCollection.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public class KotlinTestParametersCollection implements Iterable<KotlinTestParameters> {
+
+  private final Collection<KotlinTestParameters> parameters;
+
+  public KotlinTestParametersCollection(Collection<KotlinTestParameters> parameters) {
+    assert parameters != null;
+    this.parameters = parameters;
+  }
+
+  @Override
+  public Iterator<KotlinTestParameters> iterator() {
+    return parameters.iterator();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 7aacf12..460246c 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -272,7 +272,7 @@
   }
 
   @Override
-  public ProguardTestBuilder noDesugaring() {
+  public ProguardTestBuilder disableDesugaring() {
     throw new Unimplemented("No support for disabling desugaring");
   }
 
diff --git a/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java b/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
index 7a76f4a..d324f38 100644
--- a/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
+++ b/src/test/java/com/android/tools/r8/SwitchDebugLocalsConflictTest.java
@@ -33,7 +33,7 @@
   public void test() throws CompilationFailedException {
     testForD8()
         .addProgramClassFileData(Dump.dump())
-        .noDesugaring()
+        .disableDesugaring()
         .compileWithExpectedDiagnostics(
             diagnotics -> {
               diagnotics.assertNoErrors();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index b2945dc..a80cc32 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;
@@ -360,8 +360,6 @@
 
   // Actually running Proguard should only be during development.
   private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
-  // Actually running r8.jar in a forked process.
-  private static final boolean RUN_R8_JAR = System.getProperty("run_r8_jar") != null;
 
   @Rule public ExpectedException thrown = ExpectedException.none();
 
@@ -395,6 +393,10 @@
     return TestParametersBuilder.builder();
   }
 
+  public static KotlinTestParameters.Builder getKotlinTestParameters() {
+    return KotlinTestParameters.builder();
+  }
+
   protected static <S, T, E extends Throwable> Function<S, T> memoizeFunction(
       ThrowingFunction<S, T, E> fn) {
     return CacheBuilder.newBuilder()
@@ -446,13 +448,6 @@
   }
 
   /**
-   * Check if tests should run R8 in a forked process when applicable.
-   */
-  protected boolean isRunR8Jar() {
-    return RUN_R8_JAR;
-  }
-
-  /**
    * Write lines of text to a temporary file.
    *
    * The file will include a line separator after the last line.
@@ -715,7 +710,7 @@
     return AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
         readApplicationForDexOutput(app, new InternalOptions()),
         ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-        MainDexClasses.createEmptyMainDexClasses());
+        MainDexInfo.none());
   }
 
   protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
@@ -1662,6 +1657,10 @@
     return dexItemFactory.createType(descriptor(clazz));
   }
 
+  public static DexType toDexType(ClassReference classReference, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createType(classReference.getDescriptor());
+  }
+
   public static String binaryName(Class<?> clazz) {
     return DescriptorUtils.getBinaryNameFromJavaType(typeName(clazz));
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index b7ca275..ea3f393 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.google.common.base.Predicates.not;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -20,7 +21,6 @@
 import com.android.tools.r8.utils.ThrowingOutputStream;
 import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
 import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedLambdaClassesInspector;
 import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import com.google.common.base.Suppliers;
 import java.io.ByteArrayOutputStream;
@@ -124,35 +124,24 @@
   }
 
   public T addHorizontallyMergedClassesInspector(
-      Consumer<HorizontallyMergedClassesInspector> inspector) {
+      ThrowableConsumer<HorizontallyMergedClassesInspector> inspector) {
     return addOptionsModification(
         options ->
             options.testing.horizontallyMergedClassesConsumer =
                 ((dexItemFactory, horizontallyMergedClasses) ->
-                    inspector.accept(
+                    inspector.acceptWithRuntimeException(
                         new HorizontallyMergedClassesInspector(
                             dexItemFactory, horizontallyMergedClasses))));
   }
 
   public T addHorizontallyMergedClassesInspectorIf(
-      boolean condition, Consumer<HorizontallyMergedClassesInspector> inspector) {
+      boolean condition, ThrowableConsumer<HorizontallyMergedClassesInspector> inspector) {
     if (condition) {
       return addHorizontallyMergedClassesInspector(inspector);
     }
     return self();
   }
 
-  public T addHorizontallyMergedLambdaClassesInspector(
-      Consumer<HorizontallyMergedLambdaClassesInspector> inspector) {
-    return addOptionsModification(
-        options ->
-            options.testing.horizontallyMergedLambdaClassesConsumer =
-                ((dexItemFactory, horizontallyMergedLambdaClasses) ->
-                    inspector.accept(
-                        new HorizontallyMergedLambdaClassesInspector(
-                            dexItemFactory, horizontallyMergedLambdaClasses))));
-  }
-
   public T addVerticallyMergedClassesInspector(
       Consumer<VerticallyMergedClassesInspector> inspector) {
     return addOptionsModification(
@@ -323,11 +312,7 @@
   }
 
   public T disableDesugaring() {
-    return setDisableDesugaring(true);
-  }
-
-  public T setDisableDesugaring(boolean disableDesugaring) {
-    builder.setDisableDesugaring(disableDesugaring);
+    builder.setDisableDesugaring(true);
     return self();
   }
 
@@ -412,11 +397,6 @@
     return super.addLibraryProvider(provider);
   }
 
-  public T noDesugaring() {
-    builder.setDisableDesugaring(true);
-    return self();
-  }
-
   public T allowStdoutMessages() {
     allowStdoutMessages = true;
     return self();
@@ -540,6 +520,7 @@
     public void finished(DiagnosticsHandler handler) {
       mainDexClasses =
           Stream.of(builder.toString().split(System.lineSeparator()))
+              .filter(not(String::isEmpty))
               .map(
                   line -> {
                     assert line.endsWith(".class");
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 4f8713f..997faf6 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -19,8 +19,16 @@
 
   List<Diagnostic> getWarnings();
 
+  default <D extends Diagnostic> D getWarning(int index) {
+    return (D) getWarnings().get(index);
+  }
+
   List<Diagnostic> getErrors();
 
+  default <D extends Diagnostic> D getError(int index) {
+    return (D) getErrors().get(index);
+  }
+
   TestDiagnosticMessages assertNoMessages();
 
   TestDiagnosticMessages assertOnlyInfos();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 362941b..dbac9f1 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -126,39 +126,6 @@
     return addDontWarn(clazz.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
   }
 
-  @Deprecated
-  public T addDontWarnCompanionClasses() {
-    return addDontWarn("**" + COMPANION_CLASS_NAME_SUFFIX);
-  }
-
-  @Deprecated
-  public T addDontWarnCompilerSynthesizedAnnotations() {
-    return addDontWarnCompilerSynthesizedClassAnnotation()
-        .addDontWarnCompilerSynthesizedClassMapAnnotation();
-  }
-
-  @Deprecated
-  public T addDontWarnCompilerSynthesizedClassAnnotation() {
-    return addDontWarn("com.android.tools.r8.annotations.SynthesizedClass");
-  }
-
-  @Deprecated
-  public T addDontWarnCompilerSynthesizedClassMapAnnotation() {
-    return addDontWarn("com.android.tools.r8.annotations.SynthesizedClassMap");
-  }
-
-  // TODO(b/176143558): Should not report missing classes for compiler synthesized classes.
-  @Deprecated
-  public T addDontWarnEmulatedLibraryClass(Class<?> clazz) {
-    return addDontWarn(clazz.getTypeName() + "$-EL");
-  }
-
-  // TODO(b/176143558): Should not report missing classes for compiler synthesized classes.
-  @Deprecated
-  public T addDontWarnEmulatedLibraryClasses() {
-    return addDontWarn("**$-EL");
-  }
-
   public T addDontWarnGoogle() {
     return addDontWarn("com.google.**");
   }
@@ -175,10 +142,6 @@
     return addDontWarn("java.lang.invoke.*");
   }
 
-  public T addDontWarnJavaLangInvoke(String className) {
-    return addDontWarn("java.lang.invoke." + className);
-  }
-
   public T addDontWarnJavaNioFile() {
     return addDontWarn("java.nio.file.**");
   }
@@ -213,44 +176,8 @@
     return addDontWarn("kotlin.Metadata");
   }
 
-  // TODO(b/176133676): Investigate kotlinx missing class references.
-  @Deprecated
-  public T addDontWarnKotlinx() {
-    return addDontWarn("kotlinx.**");
-  }
-
-  // TODO(b/176144018): Should not report compiler synthesized references as missing.
-  @Deprecated
-  public T addDontWarnRetargetLibraryMembers() {
-    return addDontWarn("j$.retarget.$r8$retargetLibraryMember**");
-  }
-
-  @Deprecated
-  public T addDontWarnRetargetLibraryMember(String suffix) {
-    return addDontWarn("j$.retarget.$r8$retargetLibraryMember$" + suffix);
-  }
-
-  // TODO(b/154849103): Should not warn about SerializedLambda.
-  @Deprecated
-  public T addDontWarnSerializedLambda() {
-    return addDontWarn("java.lang.invoke.SerializedLambda");
-  }
-
-  // TODO(b/176781593): Should not be reported missing.
-  @Deprecated
-  public T addDontWarnTimeConversions() {
-    return addDontWarn("java.time.TimeConversions");
-  }
-
-  // TODO(b/176144018): Should not report compiler synthesized references as missing.
-  @Deprecated
-  public T addDontWarnVivifiedClass(Class<?> clazz) {
-    return addDontWarn("$-vivified-$." + clazz.getTypeName());
-  }
-
-  @Deprecated
-  public T addDontWarnVivifiedClasses() {
-    return addDontWarn("$-vivified-$.**");
+  public T addIgnoreWarnings() {
+    return addKeepRules("-ignorewarnings");
   }
 
   public T addKeepKotlinMetadata() {
@@ -422,9 +349,17 @@
   }
 
   public T addKeepAttributes(String... attributes) {
+    return addKeepAttributes(Arrays.asList(attributes));
+  }
+
+  public T addKeepAttributes(List<String> attributes) {
     return addKeepRules("-keepattributes " + String.join(",", attributes));
   }
 
+  public T addKeepAttributeExceptions() {
+    return addKeepAttributes(ProguardKeepAttributes.EXCEPTIONS);
+  }
+
   public T addKeepAttributeInnerClassesAndEnclosingMethod() {
     return addKeepAttributes(
         ProguardKeepAttributes.INNER_CLASSES, ProguardKeepAttributes.ENCLOSING_METHOD);
diff --git a/src/test/java/com/android/tools/r8/ThrowableConsumer.java b/src/test/java/com/android/tools/r8/ThrowableConsumer.java
index 664238e..f19039b 100644
--- a/src/test/java/com/android/tools/r8/ThrowableConsumer.java
+++ b/src/test/java/com/android/tools/r8/ThrowableConsumer.java
@@ -20,4 +20,11 @@
   default void acceptWithRuntimeException(Formal formal) {
     acceptWithHandler(formal, RuntimeException::new);
   }
+
+  default ThrowableConsumer<Formal> andThen(ThrowableConsumer<Formal> consumer) {
+    return formal -> {
+      accept(formal);
+      consumer.accept(formal);
+    };
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 08664a8..2ef51ed 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1058,14 +1058,8 @@
     return String.join("/", parts);
   }
 
-  private static List<String> getNamePartsForTestClass(Class clazz) {
-    List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
-    Class enclosing = clazz;
-    while (enclosing.getEnclosingClass() != null) {
-      parts.set(parts.size() - 2, parts.get(parts.size() - 2) + "$" + parts.get(parts.size() - 1));
-      parts.remove(parts.size() - 1);
-      enclosing = enclosing.getEnclosingClass();
-    }
+  private static List<String> getNamePartsForTestClass(Class<?> clazz) {
+    List<String> parts = Lists.newArrayList(clazz.getTypeName().split("\\."));
     parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ".class");
     return parts;
   }
@@ -1085,7 +1079,7 @@
         .collect(Collectors.toList());
   }
 
-  public static Path getClassFileForTestClass(Class clazz) {
+  public static Path getClassFileForTestClass(Class<?> clazz) {
     List<String> parts = getNamePartsForTestClass(clazz);
     return getClassPathForTests().resolve(Paths.get("", parts.toArray(StringUtils.EMPTY_ARRAY)));
   }
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
index 902d503..5d84adf 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
@@ -91,7 +91,6 @@
           "-keep class * { private static synthetic void lambda$*(); }");
     } else {
       builder
-          .applyIf(parameters.isDexRuntime(), TestShrinkerBuilder::addDontWarnSerializedLambda)
           .noMinification()
           .noTreeShaking();
     }
diff --git a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
index ee7ff0f..f5673dd 100644
--- a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
+++ b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
@@ -54,7 +54,6 @@
         .addProgramClasses(getClasses())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(getMainClass())
-        .applyIf(parameters.isDexRuntime(), TestShrinkerBuilder::addDontWarnSerializedLambda)
         .addPrintSeeds()
         .allowStdoutMessages()
         .noMinification()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
index f878fd5..4d62a05 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -36,9 +36,7 @@
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
-            inspector ->
-                inspector.assertMerged(A.class, B.class).assertMergedIntoDifferentType(B.class))
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
index ce0425e..029035e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
@@ -28,12 +28,8 @@
         .addOptionsModification(
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspector(
-            inspector -> {
-              if (enableHorizontalClassMerging) {
-                inspector.assertMerged(C.class, D.class).assertMergedIntoDifferentType(D.class);
-              }
-            })
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(D.class, C.class))
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
index 79aae15..03b99e0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
@@ -28,13 +28,9 @@
         .addOptionsModification(
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspector(
-            inspector -> {
-              if (enableHorizontalClassMerging) {
-                inspector.assertMerged(HelloGreeting.class, WorldGreeting.class);
-                inspector.assertMergedIntoDifferentType(WorldGreeting.class);
-              }
-            })
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector -> inspector.assertMergedInto(WorldGreeting.class, HelloGreeting.class))
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
index ff841d7..86e11d0 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -29,7 +29,7 @@
   @Parameters(name = "{0}, kotlinc: {1}")
   public static List<Object[]> setup() {
     return buildParameters(
-        TestParametersBuilder.builder().withAllRuntimes().withAllApiLevels().build(),
+        TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build(),
         getKotlinCompilers());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index 2494871..f71c916 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -174,11 +174,6 @@
                     configurationAlternative3(options, false, parameters))
         .addInnerClasses(BufferedReaderTest.class)
         .addKeepMainRule(TestClass.class)
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            builder ->
-                builder.addDontWarnRetargetLibraryMember(
-                    "virtualDispatch$BufferedReader$lines$dispatchHolder"))
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
index 33d3f35..3c822f2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -64,9 +64,6 @@
         .addKeepMainRule(Executor.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
         .compile()
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
index 7206b75..684998e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
@@ -81,12 +81,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CustomCollectionInterfaceSuperTest.class)
         .addKeepMainRule(Main.class)
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            builder ->
-                builder
-                    .addDontWarnEmulatedLibraryClass(Collection.class)
-                    .addDontWarnVivifiedClass(Predicate.class))
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index ce35ef0..2269987 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -108,9 +108,6 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(CustomCollectionSuperCallsTest.class)
             .addKeepMainRule(Executor.class)
-            .applyIf(
-                parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-                TestShrinkerBuilder::addDontWarnVivifiedClasses)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 9212761..19cd88f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -133,9 +133,6 @@
             .addInnerClasses(CustomCollectionTest.class)
             .setMinApi(parameters.getApiLevel())
             .addKeepClassAndMembersRules(Executor.class)
-            .applyIf(
-                requiresEmulatedInterfaceCoreLibDesugaring(parameters),
-                builder -> builder.addDontWarnEmulatedLibraryClasses().addDontWarnVivifiedClasses())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .inspect(this::assertCustomCollectionCallsCorrect)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 8a9fc12..4de3828 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
-import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -154,15 +153,8 @@
                             .startsWith(
                                 "Invalid parameter counts in MethodParameter attributes.")));
       }
-      // TODO(b/176900254): The nest check should not be necessary.
       new CodeInspector(desugaredLib, mapping)
-          .forAllClasses(
-              clazz ->
-                  assertTrue(
-                      clazz.getFinalName().startsWith("j$.")
-                          || clazz
-                              .getOriginalName()
-                              .startsWith(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)));
+          .forAllClasses(clazz -> assertTrue(clazz.getFinalName().startsWith("j$.")));
       return desugaredLib;
     } catch (Exception e) {
       // Don't wrap assumption violation so junit can catch it.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
index 3cf6d11..a3c1e0f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DisableDesugarTest.java
@@ -47,7 +47,7 @@
       testForD8()
           .addInnerClasses(DisableDesugarTest.class)
           .setMinApi(parameters.getApiLevel())
-          .noDesugaring()
+          .disableDesugaring()
           .enableCoreLibraryDesugaring(AndroidApiLevel.B)
           .compileWithExpectedDiagnostics(this::checkExpectedDiagnostics);
     } catch (CompilationFailedException e) {
@@ -64,7 +64,7 @@
           .addInnerClasses(DisableDesugarTest.class)
           .addKeepMainRule(TestClass.class)
           .setMinApi(parameters.getApiLevel())
-          .noDesugaring()
+          .disableDesugaring()
           .enableCoreLibraryDesugaring(AndroidApiLevel.B)
           .compileWithExpectedDiagnostics(this::checkExpectedDiagnostics);
     } catch (CompilationFailedException e) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
index 9447057..bca207f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
@@ -248,9 +248,6 @@
                   SplitterTestBase.simpleSplitProvider(
                       builder, feature2Path, temp, FeatureClass2.class))
           .addKeepAllClassesRule()
-          .applyIf(
-              parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-              TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
           .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
           .compile()
           .writeToZip(basePath);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index ff7577f..7190c34 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -265,9 +265,6 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(JavaTimeTest.class)
             .addKeepMainRule(TestClass.class)
-            .applyIf(
-                parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
-                TestShrinkerBuilder::addDontWarnRetargetLibraryMembers)
             .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
index 6c443af..ff1039f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
@@ -46,9 +46,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(MinimalInterfaceSuperTest.class)
         .addKeepMainRule(Main.class)
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            builder -> builder.addDontWarnVivifiedClass(Predicate.class))
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
index da41454..625395f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
@@ -66,9 +66,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepClassAndMembersRules(Executor.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            builder -> builder.addDontWarnEmulatedLibraryClass(Collection.class))
         .compile()
         .inspect(this::assertNoForwardingStreamMethod)
         .addDesugaredCoreLibraryRunClassPath(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 07f4415..082253b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -121,9 +121,6 @@
           testForR8(parameters.getBackend())
               .minification(minifying)
               .addKeepMainRule(TEST_CLASS)
-              .applyIf(
-                  parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-                  TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
               .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar"))
               .setMinApi(parameters.getApiLevel())
               .addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index 3eb81ed..8df7620 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -63,13 +63,6 @@
         testForR8(Backend.DEX)
             .addKeepMainRule(Executor.class)
             .addInnerClasses(RetargetOverrideTest.class)
-            .applyIf(
-                parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
-                builder ->
-                    builder
-                        .addDontWarnRetargetLibraryMembers()
-                        .addDontWarnTimeConversions()
-                        .addDontWarnVivifiedClasses())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
index 9496b9c4..27a8590 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
@@ -81,9 +81,6 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(INPUT_JAR)
         .addKeepMainRule(MAIN_CLASS)
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index 42997cb..cb8c24f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -172,9 +172,6 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-            builder -> builder.addDontWarnVivifiedClass(Consumer.class))
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
@@ -192,7 +189,6 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
-        .addDontWarnVivifiedClass(Consumer.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index 1298096..ccd3cdf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -154,10 +154,6 @@
                 opt.desugaredLibraryConfiguration =
                     configurationWithSupportAllCallbacksFromLibrary(
                         opt, false, parameters, supportAllCallbacksFromLibrary))
-        .applyIf(
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
-                && supportAllCallbacksFromLibrary,
-            builder -> builder.addDontWarnVivifiedClass(Consumer.class))
         .compile()
         .inspect(this::assertDoubleForEach)
         .inspect(this::assertWrapperMethodsPresent)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
index 8fa7780..b62045b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
@@ -84,9 +84,6 @@
             .addKeepMainRule(Executor.class)
             .addProgramClasses(Executor.class, MyMap.class)
             .addLibraryClasses(CustomLibClass.class)
-            .applyIf(
-                parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
-                builder -> builder.addDontWarnVivifiedClass(BiConsumer.class))
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .inspect(this::assertDupMethod)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
index 0e66723..70dab5d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
@@ -74,11 +74,6 @@
         testForR8(Backend.DEX)
             .addInnerClasses(GetGenericInterfaceTest.class)
             .addKeepMainRule(Executor.class)
-            .applyIf(
-                parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
-                builder ->
-                    builder.addDontWarnRetargetLibraryMember(
-                        "virtualDispatch$Date$toInstant$dispatchInterface"))
             .noMinification()
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index aa5393a..55d4763 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.desugaredlibrary.kotlin;
 
 import static com.android.tools.r8.KotlinTestBase.getCompileMemoizer;
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -16,14 +15,13 @@
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase.KotlinCompileMemoizer;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -45,30 +43,27 @@
 public class KotlinMetadataTest extends DesugaredLibraryTestBase {
 
   private static final String PKG = KotlinMetadataTest.class.getPackage().getName();
-  private final TestParameters parameters;
-  private final boolean shrinkDesugaredLibrary;
-  private final KotlinTargetVersion targetVersion;
-  private final KotlinCompiler kotlinCompiler;
   private static final String EXPECTED_OUTPUT = "Wuhuu, my special day is: 1997-8-29-2-14";
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}, target: {2}, kotlinc: {3}")
+  private final TestParameters parameters;
+  private final KotlinTestParameters kotlinParameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{0}, {1}, shrinkDesugaredLibrary: {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(),
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public KotlinMetadataTest(
-      boolean shrinkDesugaredLibrary,
       TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinCompiler) {
-    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+      KotlinTestParameters kotlinParameters,
+      boolean shrinkDesugaredLibrary) {
     this.parameters = parameters;
-    this.targetVersion = targetVersion;
-    this.kotlinCompiler = kotlinCompiler;
+    this.kotlinParameters = kotlinParameters;
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
   }
 
   private static KotlinCompileMemoizer compiledJars =
@@ -83,9 +78,9 @@
   public void testCf() throws Exception {
     assumeTrue(parameters.getRuntime().isCf());
     testForRuntime(parameters)
-        .addProgramFiles(compiledJars.getForConfiguration(kotlinCompiler, targetVersion))
-        .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinCompiler))
-        .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinCompiler))
+        .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinParameters.getCompiler()))
+        .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinParameters.getCompiler()))
         .run(parameters.getRuntime(), PKG + ".MainKt")
         .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
   }
@@ -97,9 +92,9 @@
     final File output = temp.newFile("output.zip");
     final D8TestRunResult d8TestRunResult =
         testForD8()
-            .addProgramFiles(compiledJars.getForConfiguration(kotlinCompiler, targetVersion))
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinCompiler))
-            .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinCompiler))
+            .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
+            .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinParameters.getCompiler()))
+            .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinParameters.getCompiler()))
             .setProgramConsumer(new ArchiveConsumer(output.toPath(), true))
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
@@ -126,9 +121,9 @@
     boolean desugarLibrary = parameters.isDexRuntime() && requiresAnyCoreLibDesugaring(parameters);
     final R8FullTestBuilder testBuilder =
         testForR8(parameters.getBackend())
-            .addProgramFiles(compiledJars.getForConfiguration(kotlinCompiler, targetVersion))
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinCompiler))
-            .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinCompiler))
+            .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
+            .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinParameters.getCompiler()))
+            .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinParameters.getCompiler()))
             .addKeepMainRule(PKG + ".MainKt")
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
index 35e31d5..86d541e 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
@@ -42,7 +42,7 @@
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspector(
             inspector -> {
-              inspector.assertClassNotMerged(C.class);
+              inspector.assertClassesNotMerged(C.class);
               inspector.assertMergedInto(B.class, A.class);
             })
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index bf9a226..f47e524 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -53,8 +53,7 @@
         // TODO(b/177967938): Investigate why this is needed.
         .applyIf(
             parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
-            builder -> builder.addDontWarnJavaLangInvoke().addDontWarnJavaNioFile(),
-            builder -> builder.addDontWarnJavaLangInvoke("StringConcatFactory"))
+            builder -> builder.addDontWarnJavaLangInvoke().addDontWarnJavaNioFile())
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
         .compile()
         .inspect(this::assertNotEmpty)
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
index c55df8c..6557f52 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
@@ -13,7 +13,10 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -79,9 +82,14 @@
   private boolean isNestBridge(FoundMethodSubject methodSubject) {
     DexEncodedMethod method = methodSubject.getMethod();
     if (method.isInstanceInitializer()) {
-      return method.method.proto.parameters.size() > 0 && method.method.proto.parameters.values[
-          method.method.proto.parameters.size() - 1].toSourceString()
-          .contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME);
+      if (method.method.proto.parameters.isEmpty()) {
+        return false;
+      }
+      DexType[] formals = method.method.proto.parameters.values;
+      DexType lastFormal = formals[formals.length - 1];
+      return lastFormal.isClassType()
+          && SyntheticItemsTestUtils.isInitializerTypeArgument(
+              Reference.classFromDescriptor(lastFormal.toDescriptorString()));
     }
     return method.method.name.toString()
         .startsWith(NestBasedAccessDesugaring.NEST_ACCESS_NAME_PREFIX);
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 66e2ec7..a9e05a3 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -63,9 +63,7 @@
     testMissingNestHostError(true);
     testIncompleteNestError(true);
   }
-  // TODO R8:
-  // appView.options().reportMissingNestHost(clazz);
-  // clazz.clearNestHost();
+
   @Test
   public void testWarningR8() throws Exception {
     testIncompleteNestWarning(false, parameters.isDexRuntime());
@@ -98,7 +96,6 @@
           .setMinApi(parameters.getApiLevel())
           .addProgramFiles(matchingClasses)
           .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-          .addDontWarn("java.lang.invoke.StringConcatFactory")
           .addOptionsModification(
               options -> {
                 options.ignoreMissingClasses = ignoreMissingClasses;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
new file mode 100644
index 0000000..92183a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -0,0 +1,45 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EmptyRecordTest extends TestBase {
+
+  private static final String RECORD_NAME = "EmptyRecord";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT = StringUtils.lines("Empty[]");
+
+  private final TestParameters parameters;
+
+  public EmptyRecordTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addVmArguments("--enable-preview")
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
new file mode 100644
index 0000000..e936a33
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
@@ -0,0 +1,111 @@
+// 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.desugar.records;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Remove this test when Records are supported by default. */
+@RunWith(Parameterized.class)
+public class InvalidRecordAttributeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final Backend backend;
+
+  private static final String EMPTY_RECORD = "EmptyRecord";
+  private static final byte[][] EMPTY_RECORD_PROGRAM_DATA =
+      RecordTestUtils.getProgramData(EMPTY_RECORD);
+  private static final String SIMPLE_RECORD = "SimpleRecord";
+  private static final byte[][] SIMPLE_RECORD_PROGRAM_DATA =
+      RecordTestUtils.getProgramData(SIMPLE_RECORD);
+
+  @Parameters(name = "{0} back: {1}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build(),
+        Backend.values());
+  }
+
+  public InvalidRecordAttributeTest(TestParameters parameters, Backend backend) {
+    this.parameters = parameters;
+    this.backend = backend;
+  }
+
+  @Test
+  public void testD8EmptyRecord() throws Exception {
+    Assume.assumeTrue(backend.isDex());
+    assertThrows(
+        CompilationFailedException.class,
+        () -> {
+          testForD8(backend)
+              .addProgramClassFileData(EMPTY_RECORD_PROGRAM_DATA)
+              .setMinApi(AndroidApiLevel.B)
+              .compileWithExpectedDiagnostics(
+                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
+        });
+  }
+
+  @Test
+  public void testD8SimpleRecord() throws Exception {
+    Assume.assumeTrue(backend.isDex());
+    assertThrows(
+        CompilationFailedException.class,
+        () -> {
+          testForD8(backend)
+              .addProgramClassFileData(RecordTestUtils.getProgramData(SIMPLE_RECORD))
+              .setMinApi(AndroidApiLevel.B)
+              .compileWithExpectedDiagnostics(
+                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
+        });
+  }
+
+  @Test
+  public void testR8EmptyRecord() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () -> {
+          testForR8(backend)
+              .addProgramClassFileData(EMPTY_RECORD_PROGRAM_DATA)
+              .setMinApi(AndroidApiLevel.B)
+              .addKeepMainRule(RecordTestUtils.getMainType(EMPTY_RECORD))
+              .compileWithExpectedDiagnostics(
+                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
+        });
+  }
+
+  @Test
+  public void testR8SimpleRecord() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () -> {
+          testForR8(backend)
+              .addProgramClassFileData(SIMPLE_RECORD_PROGRAM_DATA)
+              .setMinApi(AndroidApiLevel.B)
+              .addKeepMainRule(RecordTestUtils.getMainType(SIMPLE_RECORD))
+              .compileWithExpectedDiagnostics(
+                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
+        });
+  }
+
+  private static void assertUnsupportedRecordError(TestDiagnosticMessages diagnostics) {
+    diagnostics.assertErrorThatMatches(
+        diagnosticMessage(containsString("Records are not supported")));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
new file mode 100644
index 0000000..33a3b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -0,0 +1,45 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordInstanceOfTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordInstanceOf";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT = StringUtils.lines("true", "true", "false");
+
+  private final TestParameters parameters;
+
+  public RecordInstanceOfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addVmArguments("--enable-preview")
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
new file mode 100644
index 0000000..1d1e785
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -0,0 +1,58 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordInvokeCustomTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordInvokeCustom";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "Empty[]",
+          "true",
+          "true",
+          "true",
+          "true",
+          "true",
+          "false",
+          "true",
+          "true",
+          "false",
+          "false",
+          "Person[name=Jane Doe, age=42]");
+
+  private final TestParameters parameters;
+
+  public RecordInvokeCustomTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addVmArguments("--enable-preview")
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
new file mode 100644
index 0000000..1bb0a1a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -0,0 +1,55 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordReflectionTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordReflection";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "true",
+          "[]",
+          "true",
+          "[java.lang.String name, int age]",
+          "true",
+          "[java.lang.CharSequence name, int age]",
+          "[S]",
+          "false",
+          "null");
+
+  private final TestParameters parameters;
+
+  public RecordReflectionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addVmArguments("--enable-preview")
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
new file mode 100644
index 0000000..d4a804b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -0,0 +1,71 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Records are compiled using: third_party/openjdk/jdk-15/linux/bin/javac --release 15
+ * --enable-preview path/to/file.java
+ */
+public class RecordTestUtils {
+
+  private static final String EXAMPLE_FOLDER = "examplesJava15";
+  private static final String RECORD_FOLDER = "records";
+
+  public static Path jar() {
+    return Paths.get(ToolHelper.TESTS_BUILD_DIR, EXAMPLE_FOLDER, RECORD_FOLDER + ".jar");
+  }
+
+  public static byte[][] getProgramData(String mainClassSimpleName) {
+    byte[][] bytes = classDataFromPrefix(RECORD_FOLDER + "/" + mainClassSimpleName);
+    assert bytes.length > 0 : "Did not find any program data for " + mainClassSimpleName;
+    return bytes;
+  }
+
+  public static String getMainType(String mainClassSimpleName) {
+    return RECORD_FOLDER + "." + mainClassSimpleName;
+  }
+
+  private static byte[][] classDataFromPrefix(String prefix) {
+    Path examplePath = jar();
+    if (!Files.exists(examplePath)) {
+      throw new RuntimeException(
+          "Could not find path "
+              + examplePath
+              + ". Build "
+              + EXAMPLE_FOLDER
+              + " by running tools/gradle.py build"
+              + StringUtils.capitalize(EXAMPLE_FOLDER));
+    }
+    List<byte[]> result = new ArrayList<>();
+    try (ZipFile zipFile = new ZipFile(examplePath.toFile())) {
+      Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry zipEntry = entries.nextElement();
+        if (zipEntry.getName().startsWith(prefix)) {
+          result.add(ByteStreams.toByteArray(zipFile.getInputStream(zipEntry)));
+        }
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Could not read zip-entry from " + examplePath.toString(), e);
+    }
+    if (result.isEmpty()) {
+      throw new RuntimeException("Did not find any class with prefix " + prefix);
+    }
+    return result.toArray(new byte[0][0]);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
new file mode 100644
index 0000000..c0ca829
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -0,0 +1,47 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordWithMembersTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordWithMembers";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "BobX", "43", "BobX", "43", "FelixX", "-1", "FelixX", "-1", "print", "Bob43", "extra");
+
+  private final TestParameters parameters;
+
+  public RecordWithMembersTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addVmArguments("--enable-preview")
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordsAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordsAttributeTest.java
deleted file mode 100644
index 831c426..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordsAttributeTest.java
+++ /dev/null
@@ -1,87 +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.desugar.records;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.examples.jdk15.Records;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class RecordsAttributeTest extends TestBase {
-
-  private final Backend backend;
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
-    return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build(),
-        Backend.values());
-  }
-
-  public RecordsAttributeTest(TestParameters parameters, Backend backend) {
-    this.parameters = parameters;
-    this.backend = backend;
-  }
-
-  @Test
-  public void testJvm() throws Exception {
-    assumeFalse(parameters.isNoneRuntime());
-    assumeTrue(backend == Backend.CF);
-    testForJvm()
-        .addRunClasspathFiles(Records.jar())
-        .addVmArguments("--enable-preview")
-        .run(parameters.getRuntime(), Records.Main.typeName())
-        .assertSuccessWithOutputLines("Jane Doe", "42");
-  }
-
-  @Test
-  public void testD8() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForD8(backend)
-              .addProgramClassFileData(Records.Main.bytes(), Records.Main$Person.bytes())
-              .setMinApi(AndroidApiLevel.B)
-              .compileWithExpectedDiagnostics(
-                  diagnostics -> {
-                    diagnostics.assertErrorThatMatches(
-                        diagnosticMessage(containsString("Records are not supported")));
-                  });
-        });
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForR8(backend)
-              .addProgramClassFileData(Records.Main.bytes(), Records.Main$Person.bytes())
-              .setMinApi(AndroidApiLevel.B)
-              .addKeepMainRule(Records.Main.typeName())
-              .compileWithExpectedDiagnostics(
-                  diagnostics -> {
-                    diagnostics.assertErrorThatMatches(
-                        diagnosticMessage(containsString("Records are not supported")));
-                  });
-        });
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
new file mode 100644
index 0000000..35ce924
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -0,0 +1,46 @@
+// 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.desugar.records;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SimpleRecordTest extends TestBase {
+
+  private static final String RECORD_NAME = "SimpleRecord";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
+
+  private final TestParameters parameters;
+
+  public SimpleRecordTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .addVmArguments("--enable-preview")
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
index 3c48865..6f4b565 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
@@ -6,15 +6,13 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.KotlinTestBase.getCompileMemoizer;
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase.KotlinCompileMemoizer;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -29,10 +27,9 @@
 public class SimpleKotlinEnumUnboxingTest extends EnumUnboxingTestBase {
 
   private final TestParameters parameters;
+  private final KotlinTestParameters kotlinParameters;
   private final boolean enumValueOptimization;
   private final EnumKeepRules enumKeepRules;
-  private final KotlinTargetVersion targetVersion;
-  private final KotlinCompiler kotlinCompiler;
 
   private static final String PKG = SimpleKotlinEnumUnboxingTest.class.getPackage().getName();
   private static final KotlinCompileMemoizer jars =
@@ -43,27 +40,24 @@
               DescriptorUtils.getBinaryNameFromJavaType(PKG),
               "Main.kt"));
 
-  @Parameters(name = "{0}, valueOpt: {1}, keep: {2}, kotlin targetVersion: {3}, kotlinc: {4}")
+  @Parameters(name = "{0}, {1}, valueOpt: {2}, keep: {3}")
   public static List<Object[]> enumUnboxingTestParameters() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values(),
-        getAllEnumKeepRules(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getAllEnumKeepRules());
   }
 
   public SimpleKotlinEnumUnboxingTest(
       TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
       boolean enumValueOptimization,
-      EnumKeepRules enumKeepRules,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinCompiler) {
+      EnumKeepRules enumKeepRules) {
     this.parameters = parameters;
+    this.kotlinParameters = kotlinParameters;
     this.enumValueOptimization = enumValueOptimization;
     this.enumKeepRules = enumKeepRules;
-    this.targetVersion = targetVersion;
-    this.kotlinCompiler = kotlinCompiler;
   }
 
   @Test
@@ -71,8 +65,8 @@
     assumeTrue(parameters.isDexRuntime());
     testForR8(parameters.getBackend())
         .addProgramFiles(
-            jars.getForConfiguration(kotlinCompiler, targetVersion),
-            ToolHelper.getKotlinStdlibJar(kotlinCompiler))
+            jars.getForConfiguration(kotlinParameters),
+            ToolHelper.getKotlinStdlibJar(kotlinParameters.getCompiler()))
         .addKeepMainRule(PKG + ".MainKt")
         .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepRuntimeVisibleAnnotations()
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/MultipleInvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/MultipleInvokeSpecialOnSameClassTest.java
new file mode 100644
index 0000000..7c934d8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/MultipleInvokeSpecialOnSameClassTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+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 MultipleInvokeSpecialOnSameClassTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("print", "print2", "print", "print2", "print", "print2");
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MultipleInvokeSpecialOnSameClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(A.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transformMethodInsnInMethod(
+            "baz",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void print() {
+      System.out.println("print");
+    }
+
+    public void print2() {
+      System.out.println("print2");
+    }
+
+    public void bar() {
+      print(); // Will be rewritten to invoke-special A.print()
+      print2(); // Will be rewritten to invoke-special A.print2()
+      print(); // Will be rewritten to invoke-special A.print()
+      print2(); // Will be rewritten to invoke-special A.print2()
+    }
+
+    public void baz() {
+      print(); // Will be rewritten to invoke-special A.print()
+      print2(); // Will be rewritten to invoke-special A.print2()
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = new A();
+      a.bar();
+      a.baz();
+    }
+  }
+}
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/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 553a2f4..ac86504 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -25,7 +25,7 @@
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -209,7 +209,7 @@
     }
   }
 
-  static class PrimaryMethodProcessorMock extends MethodProcessor {
+  static class PrimaryMethodProcessorMock extends MethodProcessorWithWave {
 
     @Override
     public boolean shouldApplyCodeRewritings(ProgramMethod method) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCheckCastWithUnknownReturnTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCheckCastWithUnknownReturnTest.java
new file mode 100644
index 0000000..50a2f54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCheckCastWithUnknownReturnTest.java
@@ -0,0 +1,73 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverSingleCallerInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/176381203.
+@RunWith(Parameterized.class)
+public class ClassInlinerCheckCastWithUnknownReturnTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerCheckCastWithUnknownReturnTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test()
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableNeverSingleCallerInlineAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(ClassCastException.class)
+        .inspectFailure(
+            inspector -> {
+              ClassSubject aSubject = inspector.clazz(A.class);
+              assertThat(aSubject, isPresent());
+            });
+  }
+
+  public static class A {
+
+    public int number;
+
+    @NeverSingleCallerInline
+    public Object abs() {
+      if (number == 0) {
+        return new Object();
+      }
+      return this;
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = new A();
+      a.number = args.length;
+      A returnedA = (A) (a.abs());
+      System.out.println("Hello World");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java
index 99a169d..0da8e12 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java
@@ -4,19 +4,20 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverSingleCallerInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+// This is a reproduction of b/176381203.
 @RunWith(Parameterized.class)
 public class ClassInlinerDirectWithUnknownReturnTest extends TestBase {
 
@@ -31,19 +32,19 @@
     this.parameters = parameters;
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test()
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .enableNeverSingleCallerInlineAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertErrorsMatch(
-                  diagnosticMessage(
-                      containsString(
-                          "Unexpected non-trivial phi in method eligible for class inlining")));
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World 0")
+        .inspect(
+            inspector -> {
+              ClassSubject aSubject = inspector.clazz(A.class);
+              assertThat(aSubject, isPresent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 0abfe16..0fb02d7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -14,19 +14,17 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -52,16 +50,13 @@
   private final List<Path> extraClasspath = new ArrayList<>();
 
   // Some tests defined in subclasses, e.g., Metadata tests, don't care about access relaxation.
-  protected AbstractR8KotlinTestBase(
-      KotlinTargetVersion kotlinTargetVersion, KotlinCompiler kotlinc) {
-    this(kotlinTargetVersion, kotlinc, false);
+  protected AbstractR8KotlinTestBase(KotlinTestParameters kotlinParameters) {
+    this(kotlinParameters, false);
   }
 
   protected AbstractR8KotlinTestBase(
-      KotlinTargetVersion kotlinTargetVersion,
-      KotlinCompiler kotlinc,
-      boolean allowAccessModification) {
-    super(kotlinTargetVersion, kotlinc);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters);
     this.allowAccessModification = allowAccessModification;
   }
 
@@ -142,15 +137,6 @@
     return fieldSubject;
   }
 
-  protected void checkFieldIsRemoved(
-      ClassSubject classSubject, String fieldType, String fieldName) {
-    // Field must exist in the input.
-    checkFieldPresenceInInput(classSubject.getOriginalName(), fieldType, fieldName, true);
-    FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
-    assertNotNull(fieldSubject);
-    assertThat(fieldSubject, not(isPresent()));
-  }
-
   protected void checkFieldIsAbsent(ClassSubject classSubject, String fieldType, String fieldName) {
     // Field must NOT exist in the input.
     checkFieldPresenceInInput(classSubject.getOriginalName(), fieldType, fieldName, false);
@@ -159,12 +145,6 @@
     assertFalse(fieldSubject.isPresent());
   }
 
-  protected FieldSubject checkFieldIsAbsent(ClassSubject classSubject, String fieldName) {
-    FieldSubject fieldSubject = classSubject.uniqueFieldWithName(fieldName);
-    assertThat(fieldSubject, not(isPresent()));
-    return fieldSubject;
-  }
-
   protected void checkMethodIsAbsent(ClassSubject classSubject, MethodSignature methodSignature) {
     checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, false);
     checkMethodPresenceInOutput(classSubject, methodSignature, false);
@@ -187,11 +167,6 @@
     checkMethodIsKeptOrRemoved(classSubject, methodSignature, false);
   }
 
-  protected void checkMethodIsRemoved(ClassSubject classSubject, String methodName) {
-    MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
-    assertThat(methodSubject, not(isPresent()));
-  }
-
   protected MethodSubject checkMethodIsKeptOrRemoved(
       ClassSubject classSubject, MethodSignature methodSignature, boolean isPresent) {
     checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, true);
@@ -266,14 +241,13 @@
         .addProgramFiles(classpath)
         .addKeepMainRule(mainClass)
         .allowAccessModification(allowAccessModification)
-        .allowDiagnosticMessages()
+        .allowDiagnosticWarningMessages()
         .enableProguardTestOptions()
         .noMinification()
         .apply(configuration)
         .compile()
         .assertAllWarningMessagesMatch(
             containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
-        .assertAllInfoMessagesMatch(containsString("Unrecognized Kotlin lambda "))
         .run(mainClass)
         .assertSuccessWithOutput(javaResult.stdout);
   }
@@ -308,12 +282,6 @@
     }
   }
 
-  @FunctionalInterface
-  public interface AndroidAppInspector {
-
-    void inspectApp(AndroidApp androidApp) throws Exception;
-  }
-
   /**
    * Generates a "main" class which invokes the given static method (which has no argument and
    * return void type). This new class is then added to the test classpath.
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index e4622be..608b92b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -4,28 +4,20 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.KotlinTestParametersCollection;
 import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -33,7 +25,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
-import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -46,29 +37,27 @@
 @RunWith(Parameterized.class)
 public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
-  public static Collection<Object[]> data() {
-    return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static KotlinTestParametersCollection data() {
+    return getKotlinTestParameters().withAllCompilersAndTargetVersions().build();
   }
 
-  public KotlinClassInlinerTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+  public KotlinClassInlinerTest(KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters, true);
   }
 
   private static boolean isLambda(DexClass clazz) {
-    return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
-        (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz));
+    return !clazz.getType().getPackageDescriptor().startsWith("kotlin")
+        && (isKStyleLambda(clazz) || isJStyleLambda(clazz));
   }
 
-  private static boolean isKStyleLambdaOrGroup(DexClass clazz) {
-    return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
+  private static boolean isKStyleLambda(DexClass clazz) {
+    return clazz.getSuperType().getTypeName().equals("kotlin.jvm.internal.Lambda");
   }
 
-  private static boolean isJStyleLambdaOrGroup(DexClass clazz) {
-    return clazz.superType.descriptor.toString().equals("Ljava/lang/Object;") &&
-        clazz.interfaces.size() == 1;
+  private static boolean isJStyleLambda(DexClass clazz) {
+    return clazz.getSuperType().getTypeName().equals(Object.class.getTypeName())
+        && clazz.getInterfaces().size() == 1;
   }
 
   private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) {
@@ -82,9 +71,8 @@
 
   @Test
   public void testJStyleLambdas() throws Exception {
-    assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
-    final String mainClassName = "class_inliner_lambda_j_style.MainKt";
-    runTestWithDefaults(
+    String mainClassName = "class_inliner_lambda_j_style.MainKt";
+    runTest(
             "class_inliner_lambda_j_style",
             mainClassName,
             testBuilder ->
@@ -92,21 +80,32 @@
                     // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
                     .addKeepRules("-neverinline class * { void test*State*(...); }")
                     .addDontWarnJetBrainsNotNullAnnotation()
+                    .addHorizontallyMergedClassesInspector(
+                        inspector ->
+                            inspector
+                                .assertIsCompleteMergeGroup(
+                                    "class_inliner_lambda_j_style.MainKt$testStateless$1",
+                                    "class_inliner_lambda_j_style.MainKt$testStateless$2",
+                                    "class_inliner_lambda_j_style.MainKt$testStateless$3")
+                                .assertIsCompleteMergeGroup(
+                                    "class_inliner_lambda_j_style.MainKt$testStateful$1",
+                                    "class_inliner_lambda_j_style.MainKt$testStateful$2",
+                                    "class_inliner_lambda_j_style.MainKt$testStateful$2$1",
+                                    "class_inliner_lambda_j_style.MainKt$testStateful$3",
+                                    "class_inliner_lambda_j_style.MainKt$testStateful2$1",
+                                    "class_inliner_lambda_j_style.MainKt$testStateful3$1"))
                     .noClassInlining())
         .inspect(
             inspector -> {
               assertThat(
+                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
+                  isPresent());
+              assertThat(
                   inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"),
                   isPresent());
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"),
-                  isPresent());
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"),
-                  isPresent());
             });
 
-    runTestWithDefaults(
+    runTest(
             "class_inliner_lambda_j_style",
             mainClassName,
             testBuilder ->
@@ -116,40 +115,22 @@
                     .addDontWarnJetBrainsNotNullAnnotation())
         .inspect(
             inspector -> {
-              Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
-              ClassSubject clazz = inspector.clazz(mainClassName);
+              // TODO(b/173337498): MainKt$testStateless$1 should be class inlined.
+              assertThat(
+                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
+                  isPresent());
 
-              assertEquals(
-                  Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateless"));
-
-              assertEquals(
-                  Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful"));
-
+              // TODO(b/173337498): MainKt$testStateful$1 should be class inlined.
               assertThat(
                   inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"),
-                  not(isPresent()));
-
-              assertEquals(
-                  Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful2"));
-
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"),
-                  not(isPresent()));
-
-              assertEquals(
-                  Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful3"));
-
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"),
-                  not(isPresent()));
+                  isPresent());
             });
   }
 
   @Test
   public void testKStyleLambdas() throws Exception {
-    assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
-    final String mainClassName = "class_inliner_lambda_k_style.MainKt";
-    runTestWithDefaults(
+    String mainClassName = "class_inliner_lambda_k_style.MainKt";
+    runTest(
             "class_inliner_lambda_k_style",
             mainClassName,
             testBuilder ->
@@ -160,6 +141,15 @@
                         "-neverinline class * { void testBigExtraMethod(...); }",
                         "-neverinline class * { void testBigExtraMethodReturningLambda(...); }")
                     .addDontWarnJetBrainsAnnotations()
+                    .addHorizontallyMergedClassesInspector(
+                        inspector ->
+                            inspector.assertIsCompleteMergeGroup(
+                                "class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1",
+                                "class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1",
+                                "class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1",
+                                "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1",
+                                "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1",
+                                "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"))
                     .noClassInlining())
         .inspect(
             inspector -> {
@@ -174,27 +164,9 @@
               assertThat(
                   inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"),
                   isPresent());
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"),
-                  isPresent());
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"),
-                  isPresent());
-              assertThat(
-                  inspector.clazz(
-                      "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"),
-                  isPresent());
-              assertThat(
-                  inspector.clazz(
-                      "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"),
-                  isPresent());
-              assertThat(
-                  inspector.clazz(
-                      "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"),
-                  isPresent());
             });
 
-    runTestWithDefaults(
+    runTest(
             "class_inliner_lambda_k_style",
             mainClassName,
             testBuilder ->
@@ -207,20 +179,6 @@
                     .addDontWarnJetBrainsAnnotations())
         .inspect(
             inspector -> {
-              Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
-              ClassSubject clazz = inspector.clazz(mainClassName);
-
-              // TODO(b/173337498): Should be empty, but horizontal class merging interferes with
-              //  class inlining.
-              assertEquals(
-                  Sets.newHashSet(
-                      "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"),
-                  collectAccessedTypes(
-                      lambdaCheck,
-                      clazz,
-                      "testKotlinSequencesStateless",
-                      "kotlin.sequences.Sequence"));
-
               // TODO(b/173337498): Should be absent, but horizontal class merging interferes with
               //  class inlining.
               assertThat(
@@ -228,19 +186,6 @@
                       "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"),
                   isPresent());
 
-              // TODO(b/173337498): Should be empty, but horizontal class merging interferes with
-              //  class inlining.
-              assertEquals(
-                  Sets.newHashSet(
-                      "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"),
-                  collectAccessedTypes(
-                      lambdaCheck,
-                      clazz,
-                      "testKotlinSequencesStateful",
-                      "int",
-                      "int",
-                      "kotlin.sequences.Sequence"));
-
               // TODO(b/173337498): Should be absent, but horizontal class merging interferes with
               //  class inlining.
               assertThat(
@@ -248,57 +193,33 @@
                       "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"),
                   isPresent());
 
-              assertEquals(
-                  Sets.newHashSet(),
-                  collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethod"));
-
+              // TODO(b/173337498): Should be absent, but horizontal class merging interferes with
+              //  class inlining.
               assertThat(
                   inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"),
-                  not(isPresent()));
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"),
-                  not(isPresent()));
-              assertThat(
-                  inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"),
-                  not(isPresent()));
-
-              assertEquals(
-                  Sets.newHashSet(),
-                  collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda"));
-
-              assertThat(
-                  inspector.clazz(
-                      "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"),
-                  not(isPresent()));
-              assertThat(
-                  inspector.clazz(
-                      "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"),
-                  not(isPresent()));
-              assertThat(
-                  inspector.clazz(
-                      "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"),
-                  not(isPresent()));
+                  isPresent());
             });
   }
 
   @Test
   public void testDataClass() throws Exception {
-    assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
-    final String mainClassName = "class_inliner_data_class.MainKt";
-    runTestWithDefaults(
+    String mainClassName = "class_inliner_data_class.MainKt";
+    runTest(
             "class_inliner_data_class",
             mainClassName,
             TestShrinkerBuilder::addDontWarnJetBrainsAnnotations)
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(mainClassName);
-              assertTrue(
+
+              // TODO(b/141719453): Data class should maybe be class inlined.
+              assertEquals(
+                  Sets.newHashSet("class_inliner_data_class.Alpha"),
                   collectAccessedTypes(
-                          type -> !type.toSourceString().startsWith("java."),
-                          clazz,
-                          "main",
-                          String[].class.getCanonicalName())
-                      .isEmpty());
+                      type -> !type.toSourceString().startsWith("java."),
+                      clazz,
+                      "main",
+                      String[].class.getCanonicalName()));
               assertEquals(
                   Lists.newArrayList(
                       "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"),
@@ -325,39 +246,6 @@
         .collect(Collectors.toSet());
   }
 
-  private R8TestRunResult runTestWithDefaults(String folder, String mainClass) throws Exception {
-    return runTestWithDefaults(folder, mainClass, null);
-  }
-
-  private R8TestRunResult runTestWithDefaults(
-      String folder, String mainClass, ThrowableConsumer<R8FullTestBuilder> configuration)
-      throws Exception {
-    return runTest(
-        folder,
-        mainClass,
-        testBuilder ->
-            testBuilder
-                .addOptionsModification(
-                    options -> {
-                      options.enableInlining = true;
-                      options.enableLambdaMerging = false;
-
-                      // TODO(b/141719453): These limits should be removed if a possible or the test
-                      //  refactored. Tests check if specific lambdas are inlined or not, where some
-                      //  of target lambdas have at least 4 instructions.
-                      options.inliningInstructionLimit = 4;
-                      options.classInliningInstructionLimit = 40;
-
-                      // Class inlining depends on the processing order. We therefore insert all
-                      // call graph edges and verify that we can class inline everything under this
-                      // condition.
-                      options.testing.addCallEdgesForLibraryInvokes = true;
-
-                      options.horizontalClassMergerOptions().disableKotlinLambdaMerging();
-                    })
-                .apply(configuration));
-  }
-
   private List<String> collectStaticCalls(ClassSubject clazz, String methodName, String... params) {
     assertNotNull(clazz);
     MethodSignature signature = new MethodSignature(methodName, "void", params);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index e5d0d09..1598bb8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -26,15 +24,16 @@
 @RunWith(Parameterized.class)
 public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public KotlinClassStaticizerTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index 7407d46..e74d082 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -3,16 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -36,12 +34,11 @@
       o.enableInlining = false;
     };
 
-  @Parameterized.Parameters(name = "{0} target: {1}, kotlinc: {2}, allowAccessModification: {3}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
@@ -49,10 +46,9 @@
 
   public KotlinDuplicateAnnotationTest(
       TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
+      KotlinTestParameters kotlinParameters,
       boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+    super(kotlinParameters, allowAccessModification);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index eb1ca57..dae8a7d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -3,15 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -26,12 +24,11 @@
   private static final String FOLDER = "intrinsics";
   private static final String MAIN = FOLDER + ".InlineKt";
 
-  @Parameterized.Parameters(name = "{0} target: {1}, kotlinc: {2}, allowAccessModification: {3}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
@@ -39,10 +36,9 @@
 
   public KotlinIntrinsicsInlineTest(
       TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
+      KotlinTestParameters kotlinParameters,
       boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+    super(kotlinParameters, allowAccessModification);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
index 5e25b92..fa3b505 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -3,15 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
@@ -23,15 +21,16 @@
 @RunWith(Parameterized.class)
 public class KotlinUnusedArgumentsInLambdasTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public KotlinUnusedArgumentsInLambdasTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
index 6a8e196..226fb8b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
@@ -3,16 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -28,18 +26,19 @@
 @RunWith(Parameterized.class)
 public class KotlinUnusedSingletonTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   private static final String printlnSignature =
       "void java.io.PrintStream.println(java.lang.Object)";
 
   public KotlinUnusedSingletonTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index 3137d61..9f92a95 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -3,16 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import java.util.Collection;
 import org.junit.Test;
@@ -24,17 +22,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0} target: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public ProcessKotlinReflectionLibTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
index 7166aed..c2c68e4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -3,16 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -24,17 +22,15 @@
 public class ProcessKotlinStdlibTest extends KotlinTestBase {
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0} target: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
-  public ProcessKotlinStdlibTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+  public ProcessKotlinStdlibTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
@@ -71,22 +67,6 @@
   }
 
   @Test
-  public void testDontShrinkAndDontOptimizeDifferently() throws Exception {
-    test(
-        ImmutableList.of("-keep,allowobfuscation class **.*Exception*"),
-        tb ->
-            tb.addDontWarnJetBrainsAnnotations()
-                .noTreeShaking()
-                .addOptionsModification(
-                    o -> {
-                      // Randomly choose a couple of optimizations.
-                      o.enableClassInlining = false;
-                      o.enableLambdaMerging = false;
-                      o.enableValuePropagation = false;
-                    }));
-  }
-
-  @Test
   public void testDontShrinkAndDontObfuscate() throws Exception {
     test(
         ImmutableList.of("-dontshrink", "-dontobfuscate"),
@@ -99,13 +79,6 @@
   }
 
   @Test
-  public void testDontShrinkDifferently() throws Exception {
-    test(
-        ImmutableList.of("-keep,allowobfuscation class **.*Exception*"),
-        tb -> tb.addDontWarnJetBrainsAnnotations().noTreeShaking());
-  }
-
-  @Test
   public void testDontOptimize() throws Exception {
     test(ImmutableList.of("-dontoptimize"));
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 882bc22..a405400 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
@@ -68,15 +66,16 @@
           .addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE)
           .addProperty("indirectPropertyGetter", JAVA_LANG_STRING, Visibility.PRIVATE);
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public R8KotlinAccessorTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 3faf486..b2f0927 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -4,10 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -45,15 +42,16 @@
 
   private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {2}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public R8KotlinDataClassTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index 934ee16..11d1ea2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -4,10 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -26,15 +23,16 @@
   private static final TestKotlinDataClass KOTLIN_INTRINSICS_CLASS =
       new TestKotlinDataClass("kotlin.jvm.internal.Intrinsics");
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public R8KotlinIntrinsicsTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index b9fefbaf4..edd2c8f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -4,11 +4,9 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -95,15 +93,16 @@
         o.enableClassStaticizer = false;
       };
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public R8KotlinPropertiesTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index 64e3a80..c9fd417 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -3,11 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -23,15 +21,16 @@
   private static final String FOLDER = "non_null";
   private static final String STRING = "java.lang.String";
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public SimplifyIfNotNullKotlinTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
index 4a05b76..1241446 100644
--- a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -4,13 +4,11 @@
 
 package com.android.tools.r8.kotlin.coroutines;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
 import com.android.tools.r8.utils.ZipUtils;
@@ -50,19 +48,18 @@
   private Set<String> notWorkingTests =
       Sets.newHashSet("kotlinx.coroutines.test.TestDispatchersTest");
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   private final TestParameters parameters;
 
   public KotlinxCoroutinesTestRunner(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
index 7103667..787ae1f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
@@ -9,13 +9,9 @@
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.ir.optimize.lambda.kotlin.JStyleLambdaGroupIdFactory;
-import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
 import com.android.tools.r8.kotlin.lambda.JStyleKotlinLambdaMergingWithEnumUnboxingTest.Main.EnumUnboxingCandidate;
 import java.util.List;
 import org.junit.Test;
@@ -49,31 +45,17 @@
         .addDefaultRuntimeLibrary(parameters)
         .addLibraryFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.testing.kotlinLambdaMergerFactoryForClass =
-                    this::getKotlinLambdaMergerFactoryForClass)
-        .addHorizontallyMergedLambdaClassesInspector(
-            inspector -> inspector.assertMerged(Lambda1.class, Lambda2.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(Lambda2.class, Lambda1.class))
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Lambda1.method()", "Lambda2.method()");
   }
 
-  private KotlinLambdaGroupIdFactory getKotlinLambdaMergerFactoryForClass(DexProgramClass clazz) {
-    String typeName = clazz.getType().toSourceString();
-    if (typeName.equals(Lambda1.class.getTypeName())
-        || typeName.equals(Lambda2.class.getTypeName())) {
-      return JStyleLambdaGroupIdFactory.getInstance();
-    }
-    return null;
-  }
-
   static class Main {
 
     @NeverClassInline
@@ -121,7 +103,6 @@
   }
 
   @NeverClassInline
-  @NoHorizontalClassMerging
   public static final class Lambda2 implements I {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
index aea188c..fe0ef77 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
@@ -9,13 +9,9 @@
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.ir.optimize.lambda.kotlin.KStyleLambdaGroupIdFactory;
-import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
 import com.android.tools.r8.kotlin.lambda.KStyleKotlinLambdaMergingWithEnumUnboxingTest.Main.EnumUnboxingCandidate;
 import java.util.List;
 import org.junit.Test;
@@ -48,17 +44,12 @@
         .addInnerClasses(getClass())
         .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.testing.kotlinLambdaMergerFactoryForClass =
-                    this::getKotlinLambdaMergerFactoryForClass)
-        .addHorizontallyMergedLambdaClassesInspector(
-            inspector -> inspector.assertMerged(Lambda1.class, Lambda2.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(Lambda2.class, Lambda1.class))
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
         .addDontWarnJetBrainsNotNullAnnotation()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
-        .enableNoHorizontalClassMergingAnnotations()
         .noMinification()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -66,15 +57,6 @@
         .assertSuccessWithOutputLines("Lambda1.method()", "Lambda2.method()");
   }
 
-  private KotlinLambdaGroupIdFactory getKotlinLambdaMergerFactoryForClass(DexProgramClass clazz) {
-    String typeName = clazz.getType().toSourceString();
-    if (typeName.equals(Lambda1.class.getTypeName())
-        || typeName.equals(Lambda2.class.getTypeName())) {
-      return KStyleLambdaGroupIdFactory.getInstance();
-    }
-    return null;
-  }
-
   static class Main {
 
     @NeverClassInline
@@ -123,7 +105,6 @@
   }
 
   @NeverClassInline
-  @NoHorizontalClassMerging
   public static final class Lambda2 extends kotlin.jvm.internal.Lambda<kotlin.Unit>
       implements kotlin.jvm.functions.Function0<kotlin.Unit> {
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
index 4f78df2..655ddb5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -3,16 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.lambda;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.nio.file.Path;
@@ -26,17 +24,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public KotlinLambdaMergerValidationTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc, false);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters, false);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
new file mode 100644
index 0000000..0a49461
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
@@ -0,0 +1,161 @@
+// 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.kotlin.lambda;
+
+import static com.android.tools.r8.utils.PredicateUtils.not;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinLambdaMergingCapturesKotlinStyleTest extends KotlinTestBase {
+
+  private final boolean allowAccessModification;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}, {1}, allow access modification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntime(CfVm.last())
+            .withDexRuntime(Version.last())
+            .withAllApiLevels()
+            .build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
+  }
+
+  public KotlinLambdaMergingCapturesKotlinStyleTest(
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(kotlinParameters);
+    this.allowAccessModification = allowAccessModification;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeFalse(allowAccessModification);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(kotlinParameters.isFirst());
+    testForJvm()
+        .addProgramFiles(getProgramFiles())
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getProgramFiles())
+        .addKeepMainRule(getMainClassName())
+        .addDontWarnJetBrainsAnnotations()
+        .addHorizontallyMergedClassesInspector(this::inspect)
+        .allowAccessModification(allowAccessModification)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(
+            containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private void inspect(HorizontallyMergedClassesInspector inspector) throws IOException {
+    // Get the Kotlin lambdas in the input.
+    KotlinLambdasInInput lambdasInInput =
+        KotlinLambdasInInput.create(getProgramFiles(), getTestName());
+    assertEquals(0, lambdasInInput.getNumberOfJStyleLambdas());
+    assertEquals(26, lambdasInInput.getNumberOfKStyleLambdas());
+
+    // Only a subset of all K-style Kotlin lambdas are merged.
+    Set<ClassReference> unmergedLambdas =
+        ImmutableSet.of(
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test1$15"),
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$9"),
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$10"),
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(getTestName(), "MainKt$test2$11"));
+    inspector
+        .assertClassReferencesMerged(
+            lambdasInInput.getKStyleLambdas().stream()
+                .filter(not(unmergedLambdas::contains))
+                .collect(Collectors.toList()))
+        .assertClassReferencesNotMerged(unmergedLambdas);
+  }
+
+  private String getExpectedOutput() {
+    return StringUtils.lines(
+        "a: 1 2 3",
+        "b: 2 3 1",
+        "c: 3 1 2",
+        "d: 1 A D(d=x)",
+        "e: 2 D(d=y) B",
+        "f: 3 D(d=z) D(d=x)",
+        "g: 7 D(d=z) 3",
+        "h: 8 9 1",
+        "i: A B C",
+        "j: D(d=x) D(d=y) D(d=z)",
+        "k: 7 8 9",
+        "l: A D(d=y) 9",
+        "n: 7 B D(d=z)",
+        "o: D(d=x) 8 C",
+        "p: 1 2 C",
+        "a: true 10 * 20 30 40 50.0 60.0 D(d=D) S null 70",
+        "a: true 10 D(d=D) S * 20 30 40 50.0 60.0 null 70",
+        "a: true * 20 40 50.0 60.0 S null 70 10 30 D(d=D)",
+        "a: D(d=D) S null 70 true 10 * 20 30 40 50.0 60.0",
+        "a: true 10 * 20 30 40 50.0 60.0 D(d=D) S $o3 $o4",
+        "a: true 10 * 20 30 40 50.0 60.0 D(d=D) $o2 $o3 70",
+        "a: true 10 * 20 30 40 50.0 60.0 $o1 $o2 null 70",
+        "a: true 10 * 20 30 40 50.0 60.0 $o1 S null $o4",
+        "x: true 10 * 20 30 40 50.0 60.0 D(d=D) S $o3 70",
+        "y: true 10 * 20 30 40 $f 60.0 D(d=D) S null 70",
+        "z: true 10 * $s 30 40 50.0 60.0 D(d=D) S null 70");
+  }
+
+  private Path getJavaJarFile() {
+    return getJavaJarFile(getTestName());
+  }
+
+  private String getMainClassName() {
+    return getTestName() + ".MainKt";
+  }
+
+  private List<Path> getProgramFiles() {
+    Path kotlinJarFile =
+        getCompileMemoizer(getKotlinFilesInResource(getTestName()), getTestName())
+            .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect())
+            .getForConfiguration(kotlinc, targetVersion);
+    return ImmutableList.of(kotlinJarFile, getJavaJarFile());
+  }
+
+  private String getTestName() {
+    return "lambdas_kstyle_captures";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java
index 9cee958..61f417e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java
@@ -3,11 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.lambda;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.hamcrest.CoreMatchers.equalTo;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
@@ -24,14 +23,19 @@
   private static final String FOLDER = "reprocess_merged_lambdas_kstyle";
   private static final String MAIN_CLASS = "reprocess_merged_lambdas_kstyle.MainKt";
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withDexRuntimes().withAllApiLevels().build(), getKotlinCompilers());
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getKotlinTestParameters()
+            .withAllCompilers()
+            .withTargetVersion(KotlinTargetVersion.JAVA_6)
+            .build());
   }
 
-  public KotlinLambdaMergingDebugTest(TestParameters parameters, KotlinCompiler kotlinc) {
-    super(KotlinTargetVersion.JAVA_6, kotlinc);
+  public KotlinLambdaMergingDebugTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
new file mode 100644
index 0000000..a20a77d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
@@ -0,0 +1,160 @@
+// 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.kotlin.lambda;
+
+import static com.android.tools.r8.shaking.ProguardKeepAttributes.ENCLOSING_METHOD;
+import static com.android.tools.r8.shaking.ProguardKeepAttributes.INNER_CLASSES;
+import static com.android.tools.r8.shaking.ProguardKeepAttributes.SIGNATURE;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinLambdaMergingKeepAttributesKotlinStyleTest extends KotlinTestBase {
+
+  private final boolean allowAccessModification;
+  private final List<String> attributes;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}, {1}, allow access modification: {2}, attributes: {3}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntime(CfVm.last())
+            .withDexRuntime(Version.last())
+            .withAllApiLevels()
+            .build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values(),
+        ImmutableList.of(
+            Collections.emptyList(),
+            ImmutableList.of(ENCLOSING_METHOD, INNER_CLASSES),
+            ImmutableList.of(ENCLOSING_METHOD, INNER_CLASSES, SIGNATURE)));
+  }
+
+  public KotlinLambdaMergingKeepAttributesKotlinStyleTest(
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification,
+      List<String> attributes) {
+    super(kotlinParameters);
+    this.allowAccessModification = allowAccessModification;
+    this.attributes = attributes;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeFalse(allowAccessModification);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(kotlinParameters.isFirst());
+    testForJvm()
+        .addProgramFiles(getProgramFiles())
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getProgramFiles())
+        .addKeepMainRule(getMainClassName())
+        .applyIf(!attributes.isEmpty(), builder -> builder.addKeepAttributes(attributes))
+        .addDontWarnJetBrainsAnnotations()
+        .addHorizontallyMergedClassesInspector(this::inspect)
+        .allowAccessModification(allowAccessModification)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(
+            containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private void inspect(HorizontallyMergedClassesInspector inspector) throws IOException {
+    // Get the Kotlin lambdas in the input.
+    KotlinLambdasInInput lambdasInInput =
+        KotlinLambdasInInput.create(getProgramFiles(), getTestName());
+    assertEquals(0, lambdasInInput.getNumberOfJStyleLambdas());
+    assertEquals(24, lambdasInInput.getNumberOfKStyleLambdas());
+
+    // All K-style Kotlin lambdas are merged if no attributes are kept.
+    if (attributes.isEmpty()) {
+      inspector.assertClassReferencesMerged(lambdasInInput.getKStyleLambdas());
+    } else {
+      // TODO(b/179018501): allow merging classes with inner/outer classes.
+      inspector.assertClassReferencesNotMerged(lambdasInInput.getKStyleLambdas());
+    }
+  }
+
+  private String getExpectedOutput() {
+    return StringUtils.lines(
+        "Alpha(id=11)",
+        "Beta(id=12)",
+        "Gamma(payload={any}, id=13)",
+        "Alpha(id=14)",
+        "First-1-Beta(id=15)",
+        "First-2-Beta(id=16)",
+        "First-3-Beta(id=17)",
+        "First-A-Gamma(payload=18, id=19)-11",
+        "First-B-Gamma(payload=20, id=21)-11",
+        "First-C-Gamma(payload=22, id=23)-11",
+        "First-D-Gamma(payload=24, id=25)-11",
+        "First-E-Gamma(payload=26, id=27)-11",
+        "First-F-Gamma(payload=28, id=29)-11",
+        "Second-1-Beta(id=30)",
+        "Second-2-Beta(id=31)",
+        "Second-3-Beta(id=32)",
+        "Second-A-Gamma(payload=33, id=34)-22",
+        "Second-B-Gamma(payload=35, id=36)-22",
+        "Second-C-Gamma(payload=37, id=38)-22",
+        "Second-D-Gamma(payload=39, id=40)-22",
+        "Second-E-Gamma(payload=41, id=42)-22",
+        "Second-F-Gamma(payload=43, id=44)-22",
+        "4321 45 46 47",
+        "1234 Alpha(id=48) Beta(id=49) Gamma(payload=50, id=51)");
+  }
+
+  private Path getJavaJarFile() {
+    return getJavaJarFile(getTestName());
+  }
+
+  private String getMainClassName() {
+    return getTestName() + ".MainKt";
+  }
+
+  private List<Path> getProgramFiles() {
+    Path kotlinJarFile =
+        getCompileMemoizer(getKotlinFilesInResource(getTestName()), getTestName())
+            .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect())
+            .getForConfiguration(kotlinc, targetVersion);
+    return ImmutableList.of(kotlinJarFile, getJavaJarFile());
+  }
+
+  private String getTestName() {
+    return "lambdas_kstyle_generics";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
new file mode 100644
index 0000000..72abfe3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingSingletonTest.java
@@ -0,0 +1,155 @@
+// 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.kotlin.lambda;
+
+import static com.android.tools.r8.utils.PredicateUtils.not;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinLambdaMergingSingletonTest extends KotlinTestBase {
+
+  private final boolean allowAccessModification;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}, {1}, allow access modification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntime(CfVm.last())
+            .withDexRuntime(Version.last())
+            .withAllApiLevels()
+            .build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
+  }
+
+  public KotlinLambdaMergingSingletonTest(
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(kotlinParameters);
+    this.allowAccessModification = allowAccessModification;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeFalse(allowAccessModification);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(kotlinParameters.isFirst());
+    testForJvm()
+        .addProgramFiles(getProgramFiles())
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getProgramFiles())
+        .addKeepMainRule(getMainClassName())
+        .addDontWarnJetBrainsAnnotations()
+        .addHorizontallyMergedClassesInspector(this::inspect)
+        .allowAccessModification(allowAccessModification)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(
+            containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private void inspect(HorizontallyMergedClassesInspector inspector) throws IOException {
+    // Get the Kotlin lambdas in the input.
+    KotlinLambdasInInput lambdasInInput =
+        KotlinLambdasInInput.create(getProgramFiles(), getTestName());
+    assertEquals(2, lambdasInInput.getNumberOfJStyleLambdas());
+    assertEquals(7, lambdasInInput.getNumberOfKStyleLambdas());
+
+    // All J-style Kotlin lambdas should be merged into one class.
+    inspector.assertIsCompleteMergeGroup(lambdasInInput.getJStyleLambdas());
+
+    // A subset of the K-style Kotlin lambdas should be merged into one class.
+    Set<ClassReference> kStyleLambdaMergeGroup =
+        Sets.newHashSet(
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                getTestName(), "MainKt$test2$$inlined$process$1"),
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                getTestName(), "MainKt$test2$$inlined$process$2"),
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                getTestName(), "MainKt$test2$$inlined$process$3"),
+            lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                getTestName(), "MainKt$test2$$inlined$process$4"));
+    if (allowAccessModification) {
+      kStyleLambdaMergeGroup.add(
+          lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+              getTestName(), "MainKt$test2$lambda$1"));
+    }
+    inspector.assertIsCompleteMergeGroup(kStyleLambdaMergeGroup);
+
+    // The remaining lambdas are not merged.
+    inspector.assertClassReferencesNotMerged(
+        lambdasInInput.getAllLambdas().stream()
+            .filter(not(lambdasInInput::isJStyleLambda))
+            .filter(not(kStyleLambdaMergeGroup::contains))
+            .collect(Collectors.toSet()));
+  }
+
+  private String getExpectedOutput() {
+    return StringUtils.lines(
+        "(*000*001*002*003*004*005*006*007*008*009*)",
+        "(*000*001*002*003*004*005*006*007*008*009*)",
+        "(*010*011*)",
+        "(*012*013*014*)",
+        "(*015*016*)",
+        "(*017*018*019*)");
+  }
+
+  private Path getJavaJarFile() {
+    return getJavaJarFile(getTestName());
+  }
+
+  private String getMainClassName() {
+    return getTestName() + ".MainKt";
+  }
+
+  private List<Path> getProgramFiles() {
+    Path kotlinJarFile =
+        getCompileMemoizer(getKotlinFilesInResource(getTestName()), getTestName())
+            .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect())
+            .getForConfiguration(kotlinc, targetVersion);
+    return ImmutableList.of(kotlinJarFile, getJavaJarFile());
+  }
+
+  private String getTestName() {
+    return "lambdas_singleton";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
deleted file mode 100644
index 4997fe3..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ /dev/null
@@ -1,580 +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.kotlin.lambda;
-
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-import static com.google.common.base.Predicates.alwaysTrue;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.Lists;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
-  private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function";
-  private static final String KOTLIN_FUNCTION_IFACE_STR = "kotlin.jvm.functions.Function";
-
-  private void configure(InternalOptions options) {
-    options.enableClassInlining = false;
-    // The test checks that the generated lambdas inherit from Function, which is not true if
-    // the unused interface removal is enabled.
-    options.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval;
-    // Ensure that enclosing method and inner class attributes are kept even on classes that are
-    // not explicitly mentioned by a keep rule.
-    options.forceProguardCompatibility = true;
-    options.horizontalClassMergerOptions().disableKotlinLambdaMerging();
-  }
-
-  private final boolean enableUnusedInterfaceRemoval;
-
-  @Parameterized.Parameters(
-      name =
-          "target: {0}, kotlinc: {1}, allow access modification: {2}, unused interface removal:"
-              + " {3}")
-  public static Collection<Object[]> data() {
-    return buildParameters(
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
-        BooleanUtils.values(),
-        BooleanUtils.values());
-  }
-
-  public KotlinLambdaMergingTest(
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
-      boolean allowAccessModification,
-      boolean enableUnusedInterfaceRemoval) {
-    super(targetVersion, kotlinc, allowAccessModification);
-    this.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval;
-  }
-
-  abstract static class LambdaOrGroup {
-    abstract boolean match(DexClass clazz);
-  }
-
-  static class Group extends LambdaOrGroup {
-    final String pkg;
-    final String capture;
-    final int arity;
-    final String sam;
-    final int singletons;
-
-    private Group(String pkg, String capture, int arity, String sam, int singletons) {
-      this.pkg = pkg;
-      this.capture = fixCapture(capture);
-      this.arity = arity;
-      this.sam = sam;
-      this.singletons = singletons;
-    }
-
-    private String fixCapture(String capture) {
-      capture += "I";
-      char[] chars = capture.toCharArray();
-      Arrays.sort(chars);
-      return new String(chars);
-    }
-
-    @Override
-    public String toString() {
-      return "group class " +
-          (pkg.length() == 0 ? "" : pkg + "/") +
-          "-$$LambdaGroup$XXXX (arity: " + arity +
-          ", capture: " + capture + ", iface: " + sam + ", sing: " + singletons + ")";
-    }
-
-    @Override
-    boolean match(DexClass clazz) {
-      return clazz.type.getPackageDescriptor().equals(pkg) &&
-          getLambdaOrGroupCapture(clazz).equals(capture) &&
-          getLambdaSam(clazz).equals(sam) &&
-          getLambdaSingletons(clazz) == singletons &&
-          getLambdaOrGroupArity(clazz) == arity;
-    }
-  }
-
-  private static Group kstyleImpl(String pkg, String capture, int arity, int singletons) {
-    return new Group(pkg, capture, arity, KOTLIN_FUNCTION_IFACE_STR + arity, singletons);
-  }
-
-  static Group kstyle(String pkg, int arity) {
-    return kstyleImpl(pkg, "", arity, 0);
-  }
-
-  static Group kstyle(String pkg, int arity, int singletons) {
-    assertTrue(singletons != 0);
-    return kstyleImpl(pkg, "", arity, singletons);
-  }
-
-  private static Group kstyle(String pkg, String capture, int arity) {
-    assertFalse(capture.isEmpty());
-    return kstyleImpl(pkg, capture, arity, 0);
-  }
-
-  private static Group jstyleImpl(
-      String pkg, String capture, int arity, String sam, int singletons) {
-    assertTrue(capture.isEmpty() || singletons == 0);
-    return new Group(pkg, capture, arity, sam, singletons);
-  }
-
-  private static Group jstyle(String pkg, String capture, int arity, String sam) {
-    return jstyleImpl(pkg, capture, arity, sam, 0);
-  }
-
-  private static Group jstyle(String pkg, int arity, String sam, int singletons) {
-    return jstyleImpl(pkg, "", arity, sam, singletons);
-  }
-
-  static class Lambda extends LambdaOrGroup {
-    final String pkg;
-    final String name;
-    final int arity;
-
-    Lambda(String pkg, String name, int arity) {
-      this.pkg = pkg;
-      this.name = name;
-      this.arity = arity;
-    }
-
-    @Override
-    public String toString() {
-      return "lambda class " +
-          (pkg.length() == 0 ? "" : pkg + "/") +
-          name + " (arity: " + arity + ")";
-    }
-
-    @Override
-    boolean match(DexClass clazz) {
-      return clazz.type.getPackageDescriptor().equals(pkg) &&
-          clazz.type.getName().equals(name) &&
-          getLambdaOrGroupArity(clazz) == arity;
-    }
-  }
-
-  static class Verifier {
-    final CodeInspector codeInspector;
-    final List<DexClass> lambdas = new ArrayList<>();
-    final List<DexClass> groups = new ArrayList<>();
-
-    Verifier(CodeInspector codeInspector) {
-      this.codeInspector = codeInspector;
-      initGroupsAndLambdas();
-    }
-
-    private void initGroupsAndLambdas() {
-      codeInspector.forAllClasses(
-          clazz -> {
-            DexClass dexClass = clazz.getDexProgramClass();
-            if (isLambdaOrGroup(dexClass)) {
-              if (isLambdaGroupClass(dexClass)) {
-                groups.add(dexClass);
-              } else {
-                lambdas.add(dexClass);
-              }
-            }
-          });
-    }
-
-    void assertLambdaGroups(Group... groups) {
-      assertLambdasOrGroups("Lambda group", this.groups, groups);
-    }
-
-    void assertLambdas(Lambda... lambdas) {
-      assertLambdasOrGroups("Lambda", this.lambdas, lambdas);
-    }
-
-    @SafeVarargs
-    private static <T extends LambdaOrGroup>
-    void assertLambdasOrGroups(String what, List<DexClass> objects, T... checks) {
-      ArrayList<DexClass> list = Lists.newArrayList(objects);
-      for (int i = 0; i < checks.length; i++) {
-        T check = checks[i];
-        for (DexClass clazz : list) {
-          if (check.match(clazz)) {
-            // Validate static initializer.
-            if (check instanceof Group) {
-              assertEquals(
-                  clazz.getMethodCollection().numberOfDirectMethods(),
-                  ((Group) check).singletons == 0 ? 1 : 2);
-            }
-
-            list.remove(clazz);
-            checks[i] = null;
-            break;
-          }
-        }
-      }
-
-      int notFound = 0;
-      for (T check : checks) {
-        if (check != null) {
-          System.err.println(what + " not found: " + check);
-          notFound++;
-        }
-      }
-
-      for (DexClass dexClass : list) {
-        System.err.println(what + " unexpected: " +
-            dexClass.type.descriptor.toString() +
-            ", arity: " + getLambdaOrGroupArity(dexClass) +
-            ", capture: " + getLambdaOrGroupCapture(dexClass) +
-            ", sam: " + getLambdaSam(dexClass) +
-            ", sing: " + getLambdaSingletons(dexClass));
-        notFound++;
-      }
-
-      assertTrue(what + "s match failed", 0 == notFound && 0 == list.size());
-    }
-  }
-
-  private static int getLambdaOrGroupArity(DexClass clazz) {
-    if (isKStyleLambdaOrGroup(clazz)) {
-      for (DexType iface : clazz.interfaces.values) {
-        String descr = iface.descriptor.toString();
-        if (descr.startsWith(KOTLIN_FUNCTION_IFACE)) {
-          return Integer.parseInt(
-              descr.substring(KOTLIN_FUNCTION_IFACE.length(), descr.length() - 1));
-        }
-      }
-
-    } else {
-      assertTrue(isJStyleLambdaOrGroup(clazz));
-      // Taking the number of any virtual method parameters seems to be good enough.
-      assertTrue(clazz.getMethodCollection().hasVirtualMethods());
-      return clazz.lookupVirtualMethod(alwaysTrue()).method.proto.parameters.size();
-    }
-    fail("Failed to get arity for " + clazz.type.descriptor.toString());
-    throw new AssertionError();
-  }
-
-  private static String getLambdaSam(DexClass clazz) {
-    assertEquals(1, clazz.interfaces.size());
-    return clazz.interfaces.values[0].toSourceString();
-  }
-
-  private static int getLambdaSingletons(DexClass clazz) {
-    assertEquals(1, clazz.interfaces.size());
-    return clazz.staticFields().size();
-  }
-
-  private static boolean isLambdaOrGroup(DexClass clazz) {
-    return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
-        (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz));
-  }
-
-  private static boolean isKStyleLambdaOrGroup(DexClass clazz) {
-    return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
-  }
-
-  private static boolean isJStyleLambdaOrGroup(DexClass clazz) {
-    return clazz.superType.descriptor.toString().equals("Ljava/lang/Object;") &&
-        clazz.interfaces.size() == 1;
-  }
-
-  private static boolean isLambdaGroupClass(DexClass clazz) {
-    return clazz.type.getName().startsWith("-$$LambdaGroup$");
-  }
-
-  private static String getLambdaOrGroupCapture(DexClass clazz) {
-    return CaptureSignature.getCaptureSignature(clazz.instanceFields());
-  }
-
-  @Test
-  public void testTrivialKs() throws Exception {
-    final String mainClassName = "lambdas_kstyle_trivial.MainKt";
-    runTest(
-            "lambdas_kstyle_trivial",
-            mainClassName,
-            testBuilder ->
-                testBuilder
-                    .addKeepRules(
-                        "-keepunusedarguments class * extends kotlin.jvm.internal.Lambda {"
-                            + " invoke(int, short); }")
-                    .addDontWarnJetBrainsNotNullAnnotation()
-                    .addOptionsModification(this::configure))
-        .inspect(
-            inspector -> {
-              if (enableUnusedInterfaceRemoval) {
-                // Only test that the code generates the same output as the input code does on the
-                // JVM.
-                return;
-              }
-
-              Verifier verifier = new Verifier(inspector);
-              String pkg = "lambdas_kstyle_trivial";
-
-              verifier.assertLambdaGroups(
-                  allowAccessModification
-                      ? new Group[] {
-                        kstyle("", 0, 4),
-                        kstyle("", 1, 9),
-                        kstyle("", 2, 2), // -\
-                        kstyle("", 2, 5), // - 3 groups different by main method
-                        kstyle("", 2, 4), // -/
-                        kstyle("", 3, 2),
-                        kstyle("", 22, 2)
-                      }
-                      : new Group[] {
-                        kstyle(pkg, 0, 2),
-                        kstyle(pkg, 1, 5),
-                        kstyle(pkg, 2, 5), // - 2 groups different by main method
-                        kstyle(pkg, 2, 4), // -/
-                        kstyle(pkg, 3, 2),
-                        kstyle(pkg, 22, 2),
-                        kstyle(pkg + "/inner", 0, 2),
-                        kstyle(pkg + "/inner", 1, 4)
-                      });
-
-              verifier.assertLambdas(
-                  allowAccessModification
-                      ? new Lambda[] {}
-                      : new Lambda[] {
-                        new Lambda(pkg, "MainKt$testStateless$8", 2),
-                        new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)
-                      });
-            });
-  }
-
-  @Test
-  public void testCapturesKs() throws Exception {
-    final String mainClassName = "lambdas_kstyle_captures.MainKt";
-    runTest(
-            "lambdas_kstyle_captures",
-            mainClassName,
-            testBuilder ->
-                testBuilder
-                    .addDontWarnJetBrainsAnnotations()
-                    .addOptionsModification(this::configure))
-        .inspect(
-            inspector -> {
-              if (enableUnusedInterfaceRemoval) {
-                // Only test that the code generates the same output as the input code does on the
-                // JVM.
-                return;
-              }
-
-              Verifier verifier = new Verifier(inspector);
-              String pkg = "lambdas_kstyle_captures";
-              String grpPkg = allowAccessModification ? "" : pkg;
-
-              verifier.assertLambdaGroups(
-                  kstyle(grpPkg, "LLL", 0),
-                  kstyle(grpPkg, "ILL", 0),
-                  kstyle(grpPkg, "III", 0),
-                  kstyle(grpPkg, "BCDFIJLLLLSZ", 0),
-                  kstyle(grpPkg, "BCDFIJLLSZ", 0));
-
-              verifier.assertLambdas(
-                  new Lambda(pkg, "MainKt$test1$15", 0),
-                  new Lambda(pkg, "MainKt$test2$10", 0),
-                  new Lambda(pkg, "MainKt$test2$11", 0),
-                  new Lambda(pkg, "MainKt$test2$9", 0));
-            });
-  }
-
-  @Test
-  public void testGenericsNoSignatureKs() throws Exception {
-    final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest(
-            "lambdas_kstyle_generics",
-            mainClassName,
-            testBuilder ->
-                testBuilder
-                    .addDontWarnJetBrainsAnnotations()
-                    .addOptionsModification(this::configure))
-        .inspect(
-            inspector -> {
-              if (enableUnusedInterfaceRemoval) {
-                // Only test that the code generates the same output as the input code does on the
-                // JVM.
-                return;
-              }
-
-              Verifier verifier = new Verifier(inspector);
-              String pkg = "lambdas_kstyle_generics";
-              String grpPkg = allowAccessModification ? "" : pkg;
-
-              verifier.assertLambdaGroups(
-                  kstyle(grpPkg, 1, 3), // Group for Any
-                  kstyle(grpPkg, "L", 1), // Group for Beta
-                  kstyle(grpPkg, "LS", 1), // Group for Gamma
-                  kstyle(grpPkg, 1, 2) // Group for int
-                  );
-
-              verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1));
-            });
-  }
-
-  @Test
-  public void testInnerClassesAndEnclosingMethodsKs() throws Exception {
-    final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest(
-            "lambdas_kstyle_generics",
-            mainClassName,
-            testBuilder ->
-                testBuilder
-                    .addDontWarnJetBrainsAnnotations()
-                    .addKeepAttributeInnerClassesAndEnclosingMethod()
-                    .addOptionsModification(this::configure))
-        .inspect(
-            inspector -> {
-              if (enableUnusedInterfaceRemoval) {
-                // Only test that the code generates the same output as the input code does on the
-                // JVM.
-                return;
-              }
-
-              Verifier verifier = new Verifier(inspector);
-              String pkg = "lambdas_kstyle_generics";
-              String grpPkg = allowAccessModification ? "" : pkg;
-
-              verifier.assertLambdaGroups(
-                  kstyle(grpPkg, 1, 3), // Group for Any
-                  kstyle(grpPkg, "L", 1), // Group for Beta   // First
-                  kstyle(grpPkg, "L", 1), // Group for Beta   // Second
-                  kstyle(grpPkg, "LS", 1), // Group for Gamma // First
-                  kstyle(grpPkg, "LS", 1), // Group for Gamma // Second
-                  kstyle(grpPkg, 1, 2) // Group for int
-                  );
-
-              verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1));
-            });
-  }
-
-  @Test
-  public void testGenericsSignatureInnerEnclosingKs() throws Exception {
-    final String mainClassName = "lambdas_kstyle_generics.MainKt";
-    runTest(
-            "lambdas_kstyle_generics",
-            mainClassName,
-            // KEEP_SIGNATURE_INNER_ENCLOSING,
-            testBuilder ->
-                testBuilder
-                    .addDontWarnJetBrainsAnnotations()
-                    .addKeepAttributeInnerClassesAndEnclosingMethod()
-                    .addKeepAttributeSignature()
-                    .addOptionsModification(this::configure))
-        .inspect(
-            inspector -> {
-              if (enableUnusedInterfaceRemoval) {
-                // Only test that the code generates the same output as the input code does on the
-                // JVM.
-                return;
-              }
-
-              Verifier verifier = new Verifier(inspector);
-              String pkg = "lambdas_kstyle_generics";
-              String grpPkg = allowAccessModification ? "" : pkg;
-
-              verifier.assertLambdaGroups(
-                  kstyle(grpPkg, 1, 3), // Group for Any
-                  kstyle(grpPkg, "L", 1), // Group for Beta in First
-                  kstyle(grpPkg, "L", 1), // Group for Beta in Second
-                  kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in First
-                  kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in First
-                  kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in Second
-                  kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second
-                  kstyle(grpPkg, 1, 2) // Group for int
-                  );
-
-              verifier.assertLambdas(new Lambda(pkg, "MainKt$main$4", 1));
-            });
-  }
-
-  @Test
-  public void testTrivialJs() throws Exception {
-    final String mainClassName = "lambdas_jstyle_trivial.MainKt";
-    runTest(
-            "lambdas_jstyle_trivial",
-            mainClassName,
-            testBuilder ->
-                testBuilder
-                    .addDontWarnJetBrainsAnnotations()
-                    .addOptionsModification(this::configure))
-        .inspect(
-            inspector -> {
-              Verifier verifier = new Verifier(inspector);
-              String pkg = "lambdas_jstyle_trivial";
-              String grp = allowAccessModification ? "" : pkg;
-
-              String supplier = "lambdas_jstyle_trivial.Lambdas$Supplier";
-              String intSupplier = "lambdas_jstyle_trivial.Lambdas$IntSupplier";
-              String consumer = "lambdas_jstyle_trivial.Lambdas$Consumer";
-              String intConsumer = "lambdas_jstyle_trivial.Lambdas$IntConsumer";
-              String multiFunction = "lambdas_jstyle_trivial.Lambdas$MultiFunction";
-
-              verifier.assertLambdaGroups(
-                  jstyle(grp, 0, intSupplier, 2),
-                  jstyle(grp, "L", 0, supplier),
-                  jstyle(grp, "LL", 0, supplier),
-                  jstyle(grp, "LLL", 0, supplier),
-                  jstyle(grp, 1, intConsumer, allowAccessModification ? 3 : 2),
-                  jstyle(grp, "I", 1, consumer),
-                  jstyle(grp, "II", 1, consumer),
-                  jstyle(grp, "III", 1, consumer),
-                  jstyle(grp, "IIII", 1, consumer),
-                  jstyle(grp, 3, multiFunction, 2),
-                  jstyle(grp, 3, multiFunction, 2),
-                  jstyle(grp, 3, multiFunction, 4),
-                  jstyle(grp, 3, multiFunction, 6));
-
-              verifier.assertLambdas(
-                  allowAccessModification
-                      ? new Lambda[] {
-                        new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1),
-                        new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1)
-                      }
-                      : new Lambda[] {
-                        new Lambda(pkg + "/inner", "InnerKt$testInner1$1", 1),
-                        new Lambda(pkg + "/inner", "InnerKt$testInner1$2", 1),
-                        new Lambda(pkg + "/inner", "InnerKt$testInner1$3", 1),
-                        new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1),
-                        new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1)
-                      });
-            });
-  }
-
-  @Test
-  public void testSingleton() throws Exception {
-    final String mainClassName = "lambdas_singleton.MainKt";
-    runTest(
-            "lambdas_singleton",
-            mainClassName,
-            testBuilder ->
-                testBuilder
-                    .addDontWarnJetBrainsAnnotations()
-                    .addOptionsModification(this::configure)
-                    .noHorizontalClassMerging())
-        .inspect(
-            inspector -> {
-              Verifier verifier = new Verifier(inspector);
-              String pkg = "lambdas_singleton";
-              String grp = allowAccessModification ? "" : pkg;
-
-              verifier.assertLambdaGroups(
-                  kstyle(grp, 1 /* 1 out of 5 lambdas in the group */),
-                  jstyle(grp, 2, "java.util.Comparator", 0 /* 0 out of 2 lambdas in the group */));
-
-              verifier.assertLambdas(/* None */ );
-            });
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
new file mode 100644
index 0000000..1e83c00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -0,0 +1,210 @@
+// 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.kotlin.lambda;
+
+import static com.android.tools.r8.utils.PredicateUtils.not;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinLambdaMergingTrivialJavaStyleTest extends KotlinTestBase {
+
+  private final boolean allowAccessModification;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}, {1}, allow access modification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntime(CfVm.last())
+            .withDexRuntime(Version.last())
+            .withAllApiLevels()
+            .build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
+  }
+
+  public KotlinLambdaMergingTrivialJavaStyleTest(
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(kotlinParameters);
+    this.allowAccessModification = allowAccessModification;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeFalse(allowAccessModification);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(kotlinParameters.isFirst());
+    testForJvm()
+        .addProgramFiles(getProgramFiles())
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getProgramFiles())
+        .addKeepMainRule(getMainClassName())
+        .addDontWarnJetBrainsAnnotations()
+        .addHorizontallyMergedClassesInspector(this::inspect)
+        .allowAccessModification(allowAccessModification)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(
+            containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private void inspect(HorizontallyMergedClassesInspector inspector) throws IOException {
+    // Get the Kotlin lambdas in the input.
+    KotlinLambdasInInput lambdasInInput =
+        KotlinLambdasInInput.create(getProgramFiles(), getTestName());
+    assertEquals(39, lambdasInInput.getNumberOfJStyleLambdas());
+    assertEquals(0, lambdasInInput.getNumberOfKStyleLambdas());
+
+    if (!allowAccessModification) {
+      // Only a subset of all J-style Kotlin lambdas are merged without -allowaccessmodification.
+      Set<ClassReference> unmergedLambdas =
+          ImmutableSet.of(
+              lambdasInInput.getJStyleLambdaReferenceFromTypeName(
+                  getTestName(), "inner.InnerKt$testInner1$1"),
+              lambdasInInput.getJStyleLambdaReferenceFromTypeName(
+                  getTestName(), "inner.InnerKt$testInner1$2"),
+              lambdasInInput.getJStyleLambdaReferenceFromTypeName(
+                  getTestName(), "inner.InnerKt$testInner1$3"),
+              lambdasInInput.getJStyleLambdaReferenceFromTypeName(
+                  getTestName(), "inner.InnerKt$testInner1$4"),
+              lambdasInInput.getJStyleLambdaReferenceFromTypeName(
+                  getTestName(), "inner.InnerKt$testInner1$5"));
+      inspector
+          .assertClassReferencesMerged(
+              lambdasInInput.getJStyleLambdas().stream()
+                  .filter(not(unmergedLambdas::contains))
+                  .collect(Collectors.toList()))
+          .assertClassReferencesNotMerged(unmergedLambdas);
+      return;
+    }
+
+    // All J-style Kotlin lambdas are merged with -allowaccessmodification.
+    inspector.assertClassReferencesMerged(lambdasInInput.getJStyleLambdas());
+  }
+
+  private String getExpectedOutput() {
+    return StringUtils.lines(
+        "{005:4}",
+        "{007:6}",
+        "009:{008}:{0}",
+        "011:{010}:{0}",
+        "013:{012}:{0}:{1}",
+        "015:{014}:{0}:{1}",
+        "017:{Local(id=016)}:{0}:{1}:{2}",
+        "019:{Local(id=018)}:{0}:{1}:{2}",
+        "021:{Local(id=Local(id=020))}:{0}:{1}:{2}:{3}",
+        "023:{Local(id=Local(id=022))}:{0}:{1}:{2}:{3}",
+        "27",
+        "kotlin.Unit",
+        "28",
+        "kotlin.Unit",
+        "029:024",
+        "kotlin.Unit",
+        "030:024",
+        "kotlin.Unit",
+        "031:024",
+        "kotlin.Unit",
+        "032:024",
+        "kotlin.Unit",
+        "Local(id=033):024:025",
+        "kotlin.Unit",
+        "Local(id=034):024:025",
+        "kotlin.Unit",
+        "Local(id=Local(id=035)):024:025:026",
+        "kotlin.Unit",
+        "Local(id=Local(id=036)):024:025:026",
+        "kotlin.Unit",
+        "037:038:039",
+        "039:037:038",
+        "038:039:037",
+        "Local(id=037):038:039",
+        "Local(id=038):037:039",
+        "037:Local(id=038):039",
+        "037:Local(id=039):038",
+        "Local(id=Local(id=037)):Local(id=Local(id=038)):Local(id=Local(id=039))",
+        "Local(id=Local(id=039)):Local(id=Local(id=037)):Local(id=Local(id=038))",
+        "040:Local(id=041):Local(id=Local(id=042))",
+        "Local(id=Local(id=042)):040:Local(id=041)",
+        "Local(id=041):Local(id=Local(id=042)):040",
+        "Local(id=040):Local(id=041):Local(id=Local(id=042))",
+        "Local(id=Local(id=041)):040:Local(id=Local(id=042))",
+        "040:Local(id=Local(id=041)):Local(id=Local(id=042))",
+        "040:Local(id=Local(id=Local(id=042))):Local(id=041)",
+        "Local(id=Local(id=040)):Local(id=Local(id=Local(id=041))):Local(id=Local(id=Local(id=Local(id=042))))",
+        "Local(id=Local(id=Local(id=Local(id=042)))):Local(id=Local(id=040)):Local(id=Local(id=Local(id=041)))",
+        "043:044:045",
+        "045:043:044",
+        "044:045:043",
+        "Local(id=043):044:045",
+        "Local(id=044):043:045",
+        "046:Local(id=047):Local(id=048)",
+        "Local(id=048):046:Local(id=047)",
+        "Local(id=047):Local(id=048):046",
+        "Local(id=046):Local(id=047):Local(id=048)",
+        "Local(id=Local(id=047)):046:Local(id=048)",
+        "{053:100}",
+        "055:{054}:{49}",
+        "057:{056}:{49}:{50}",
+        "059:{InnerLocal(id=058)}:{49}:{50}:{51}",
+        "061:{InnerLocal(id=InnerLocal(id=060))}:{49}:{50}:{51}:{52");
+  }
+
+  private Path getJavaJarFile() {
+    return getJavaJarFile(getTestName());
+  }
+
+  private String getMainClassName() {
+    return getTestName() + ".MainKt";
+  }
+
+  private List<Path> getProgramFiles() {
+    Path kotlinJarFile =
+        getCompileMemoizer(getKotlinFilesInResource(getTestName()), getTestName())
+            .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect())
+            .getForConfiguration(kotlinc, targetVersion);
+    return ImmutableList.of(kotlinJarFile, getJavaJarFile());
+  }
+
+  private String getTestName() {
+    return "lambdas_jstyle_trivial";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java
new file mode 100644
index 0000000..3515109
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialKotlinStyleTest.java
@@ -0,0 +1,171 @@
+// 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.kotlin.lambda;
+
+import static com.android.tools.r8.utils.PredicateUtils.not;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KotlinLambdaMergingTrivialKotlinStyleTest extends KotlinTestBase {
+
+  private final boolean allowAccessModification;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}, {1}, allow access modification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntime(CfVm.last())
+            .withDexRuntime(Version.last())
+            .withAllApiLevels()
+            .build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
+  }
+
+  public KotlinLambdaMergingTrivialKotlinStyleTest(
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(kotlinParameters);
+    this.allowAccessModification = allowAccessModification;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeFalse(allowAccessModification);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(kotlinParameters.isFirst());
+    testForJvm()
+        .addProgramFiles(getProgramFiles())
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getProgramFiles())
+        .addKeepMainRule(getMainClassName())
+        .addDontWarnJetBrainsNotNullAnnotation()
+        .addHorizontallyMergedClassesInspector(this::inspect)
+        .allowAccessModification(allowAccessModification)
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(
+            containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .run(parameters.getRuntime(), getMainClassName())
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private void inspect(HorizontallyMergedClassesInspector inspector) throws IOException {
+    // Get the Kotlin lambdas in the input.
+    KotlinLambdasInInput lambdasInInput =
+        KotlinLambdasInInput.create(getProgramFiles(), getTestName());
+    assertEquals(0, lambdasInInput.getNumberOfJStyleLambdas());
+    assertEquals(28, lambdasInInput.getNumberOfKStyleLambdas());
+
+    if (!allowAccessModification) {
+      // Only a subset of all K-style Kotlin lambdas are merged without -allowaccessmodification.
+      Set<ClassReference> unmergedLambdas =
+          ImmutableSet.of(
+              lambdasInInput.getKStyleLambdaReferenceFromTypeName(
+                  getTestName(), "inner.InnerKt$testInnerStateless$7"));
+      inspector
+          .assertClassReferencesMerged(
+              lambdasInInput.getKStyleLambdas().stream()
+                  .filter(not(unmergedLambdas::contains))
+                  .collect(Collectors.toList()))
+          .assertClassReferencesNotMerged(unmergedLambdas);
+      return;
+    }
+
+    // All K-style Kotlin lambdas are merged with -allowaccessmodification.
+    inspector.assertClassReferencesMerged(lambdasInInput.getKStyleLambdas());
+  }
+
+  private String getExpectedOutput() {
+    return StringUtils.lines(
+        "first empty",
+        "second empty",
+        "first single",
+        "second single",
+        "third single",
+        "caught: exception#14",
+        "15",
+        "16-17",
+        "181920",
+        "one-two-three",
+        "one-two-...-twentythree",
+        "46474849505152535455565758596061626364656667",
+        "first empty",
+        "second empty",
+        "first single",
+        "second single",
+        "third single",
+        "71",
+        "72-73",
+        "1",
+        "5",
+        "8",
+        "20",
+        "5",
+        "",
+        "kotlin.Unit",
+        "10",
+        "kotlin.Unit",
+        "13",
+        "kotlin.Unit",
+        "14 -- 10",
+        "kotlin.Unit");
+  }
+
+  private Path getJavaJarFile() {
+    return getJavaJarFile(getTestName());
+  }
+
+  private String getMainClassName() {
+    return getTestName() + ".MainKt";
+  }
+
+  private List<Path> getProgramFiles() {
+    Path kotlinJarFile =
+        getCompileMemoizer(getKotlinFilesInResource(getTestName()), getTestName())
+            .configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect())
+            .getForConfiguration(kotlinc, targetVersion);
+    return ImmutableList.of(kotlinJarFile, getJavaJarFile());
+  }
+
+  private String getTestName() {
+    return "lambdas_kstyle_trivial";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
index b8fb366..8258582 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
@@ -3,10 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.lambda;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.Collection;
@@ -17,15 +15,16 @@
 @RunWith(Parameterized.class)
 public class KotlinLambdaMergingWithReprocessingTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public KotlinLambdaMergingWithReprocessingTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
@@ -34,14 +33,6 @@
     runTest(
         "reprocess_merged_lambdas_kstyle",
         mainClassName,
-        testBuilder ->
-            testBuilder
-                .addDontWarnJetBrainsNotNullAnnotation()
-                .addOptionsModification(
-                    options -> {
-                      options.enableInlining = true;
-                      options.enableClassInlining = true;
-                      options.enableLambdaMerging = true;
-                    }));
+        TestShrinkerBuilder::addDontWarnJetBrainsNotNullAnnotation);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
index 5032ea2..087a81b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -3,10 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.lambda;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.Collection;
@@ -17,15 +14,16 @@
 @RunWith(Parameterized.class)
 public class KotlinLambdaMergingWithSmallInliningBudgetTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}")
+  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(), getKotlinCompilers(), BooleanUtils.values());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
+        BooleanUtils.values());
   }
 
   public KotlinLambdaMergingWithSmallInliningBudgetTest(
-      KotlinTargetVersion targetVersion, KotlinCompiler kotlinc, boolean allowAccessModification) {
-    super(targetVersion, kotlinc, allowAccessModification);
+      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+    super(kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdasInInput.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdasInInput.java
new file mode 100644
index 0000000..3517ff3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdasInInput.java
@@ -0,0 +1,125 @@
+// 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.kotlin.lambda;
+
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class KotlinLambdasInInput {
+
+  private final Set<ClassReference> jStyleLambdas;
+  private final Set<ClassReference> kStyleLambdas;
+
+  private KotlinLambdasInInput(
+      Set<ClassReference> jStyleLambdas, Set<ClassReference> kStyleLambdas) {
+    this.jStyleLambdas = jStyleLambdas;
+    this.kStyleLambdas = kStyleLambdas;
+  }
+
+  public static KotlinLambdasInInput create(List<Path> programFiles, String testName)
+      throws IOException {
+    CodeInspector inputInspector = new CodeInspector(programFiles);
+    Set<ClassReference> jStyleLambdas = new HashSet<>();
+    Set<ClassReference> kStyleLambdas = new HashSet<>();
+    for (FoundClassSubject classSubject : inputInspector.allClasses()) {
+      DexProgramClass clazz = classSubject.getDexProgramClass();
+      if (!clazz.getType().getPackageName().startsWith(testName)) {
+        continue;
+      }
+      if (internalIsJStyleLambda(clazz)) {
+        jStyleLambdas.add(Reference.classFromTypeName(clazz.getTypeName()));
+      } else if (internalIsKStyleLambda(clazz)) {
+        kStyleLambdas.add(Reference.classFromTypeName(clazz.getTypeName()));
+      }
+    }
+    return new KotlinLambdasInInput(jStyleLambdas, kStyleLambdas);
+  }
+
+  private static boolean internalIsKStyleLambda(DexProgramClass clazz) {
+    return clazz.getSuperType().getTypeName().equals("kotlin.jvm.internal.Lambda");
+  }
+
+  private static boolean internalIsJStyleLambda(DexProgramClass clazz) {
+    if (!clazz.getSuperType().getTypeName().equals(Object.class.getTypeName())
+        || clazz.getInterfaces().size() != 1
+        || clazz.getMethodCollection().numberOfVirtualMethods() == 0) {
+      return false;
+    }
+    if (clazz
+        .getMethodCollection()
+        .hasDirectMethods(method -> method.isStatic() && !method.isClassInitializer())) {
+      return false;
+    }
+    int numberOfFinalNonBridgeNonSyntheticMethods = 0;
+    for (DexEncodedMethod method : clazz.virtualMethods()) {
+      if (method.isFinal() && !method.isBridge() && !method.isSyntheticMethod()) {
+        numberOfFinalNonBridgeNonSyntheticMethods++;
+      }
+    }
+    return numberOfFinalNonBridgeNonSyntheticMethods == 1;
+  }
+
+  public Set<ClassReference> getAllLambdas() {
+    return SetUtils.newIdentityHashSet(jStyleLambdas, kStyleLambdas);
+  }
+
+  public Set<ClassReference> getJStyleLambdas() {
+    return jStyleLambdas;
+  }
+
+  public ClassReference getJStyleLambdaReferenceFromTypeName(String testName, String simpleName) {
+    ClassReference classReference = Reference.classFromTypeName(testName + "." + simpleName);
+    assertTrue(jStyleLambdas.contains(classReference));
+    return classReference;
+  }
+
+  public Set<ClassReference> getKStyleLambdas() {
+    return kStyleLambdas;
+  }
+
+  public ClassReference getKStyleLambdaReferenceFromTypeName(String testName, String simpleName) {
+    ClassReference classReference = Reference.classFromTypeName(testName + "." + simpleName);
+    assertTrue(
+        "Class is not a Kotlin-style lambda: " + classReference.getTypeName(),
+        kStyleLambdas.contains(classReference));
+    return classReference;
+  }
+
+  public int getNumberOfJStyleLambdas() {
+    return jStyleLambdas.size();
+  }
+
+  public int getNumberOfKStyleLambdas() {
+    return kStyleLambdas.size();
+  }
+
+  public boolean isJStyleLambda(ClassReference classReference) {
+    return jStyleLambdas.contains(classReference);
+  }
+
+  public boolean isKStyleLambda(ClassReference classReference) {
+    return kStyleLambdas.contains(classReference);
+  }
+
+  public void print() {
+    System.out.println("Java-style Kotlin lambdas:");
+    jStyleLambdas.forEach(lambda -> System.out.println(lambda.getTypeName()));
+    System.out.println();
+    System.out.println("Kotlin-style Kotlin lambdas:");
+    kStyleLambdas.forEach(lambda -> System.out.println(lambda.getTypeName()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
deleted file mode 100644
index 26cc829..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinxMetadataExtensionsServiceTest.java
+++ /dev/null
@@ -1,107 +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.kotlin.lambda;
-
-import static com.android.tools.r8.ToolHelper.EXAMPLES_KOTLIN_RESOURCE_DIR;
-import static com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.kstyle;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Group;
-import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Lambda;
-import com.android.tools.r8.kotlin.lambda.KotlinLambdaMergingTest.Verifier;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import org.junit.Test;
-
-public class KotlinxMetadataExtensionsServiceTest extends TestBase {
-
-  private void forkR8_kstyle_trivial(boolean allowAccessModification) throws Exception {
-    if (!isRunR8Jar()) {
-      return;
-    }
-    Path working = temp.getRoot().toPath();
-    Path kotlinJar =
-        Paths.get(EXAMPLES_KOTLIN_RESOURCE_DIR, "JAVA_8", "lambdas_kstyle_trivial.jar")
-            .toAbsolutePath();
-    Path output = working.resolve("classes.dex");
-    assertFalse(Files.exists(output));
-    Path proguardConfiguration = temp.newFile("test.conf").toPath();
-    List<String> lines = ImmutableList.of(
-        "-keepattributes Signature,InnerClasses,EnclosingMethod",
-        "-keep class **MainKt {",
-        "  public static void main(...);",
-        "}",
-        "-printmapping",
-        "-dontobfuscate",
-        allowAccessModification ? "-allowaccessmodification" : ""
-    );
-    FileUtils.writeTextFile(proguardConfiguration, lines);
-    ProcessResult result = ToolHelper.forkR8Jar(working,
-        "--pg-conf", proguardConfiguration.toString(),
-        "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.O).toAbsolutePath().toString(),
-        kotlinJar.toString());
-    assertEquals(0, result.exitCode);
-    assertThat(result.stderr, not(containsString(
-        "No MetadataExtensions instances found in the classpath")));
-    assertTrue(Files.exists(output));
-
-    CodeInspector inspector = new CodeInspector(output);
-    Verifier verifier = new Verifier(inspector);
-    String pkg = "lambdas_kstyle_trivial";
-    verifier.assertLambdaGroups(
-        allowAccessModification
-            ? new Group[] {
-              kstyle("", 0, 4),
-              kstyle("", 1, 9),
-              kstyle("", 2, 2), // -\
-              kstyle("", 2, 5), // - 3 groups different by main method
-              kstyle("", 2, 4), // -/
-              kstyle("", 3, 2),
-              kstyle("", 22, 2)
-            }
-            : new Group[] {
-              kstyle(pkg, 0, 2),
-              kstyle(pkg, 1, 5),
-              kstyle(pkg, 2, 5), // - 2 groups different by main method
-              kstyle(pkg, 2, 4), // -/
-              kstyle(pkg, 3, 2),
-              kstyle(pkg, 22, 2),
-              kstyle(pkg + "/inner", 0, 2),
-              kstyle(pkg + "/inner", 1, 4)
-            });
-
-    verifier.assertLambdas(
-        allowAccessModification
-            ? new Lambda[] {}
-            : new Lambda[] {
-              new Lambda(pkg, "MainKt$testStateless$8", 2),
-              new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)
-            });
-  }
-
-  @Test
-  public void testTrivialKs_allowAccessModification() throws Exception {
-    forkR8_kstyle_trivial(true);
-  }
-
-  @Test
-  public void testTrivialKs_notAllowAccessModification() throws Exception {
-    forkR8_kstyle_trivial(false);
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
index e9f4e46..ac22198 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
@@ -4,31 +4,19 @@
 
 package com.android.tools.r8.kotlin.lambda.b148525512;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -57,17 +45,15 @@
               });
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
-  public B148525512(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+  public B148525512(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
@@ -84,35 +70,6 @@
     }
   }
 
-  private void checkLambdaGroups(CodeInspector inspector) {
-    List<FoundClassSubject> lambdaGroups =
-        inspector.allClasses().stream()
-            .filter(clazz -> clazz.getOriginalName().contains("LambdaGroup"))
-            .collect(Collectors.toList());
-    assertEquals(1, lambdaGroups.size());
-    MethodSubject invokeMethod = lambdaGroups.get(0).uniqueMethodWithName("invoke");
-    assertThat(invokeMethod, isPresent());
-    // The lambda group has 2 captures which capture "Base".
-    assertEquals(
-        2,
-        invokeMethod
-            .streamInstructions()
-            .filter(InstructionSubject::isCheckCast)
-            .filter(
-                instruction ->
-                    instruction.asCheckCast().getType().toSourceString().contains("Base"))
-            .count());
-    // The lambda group has no captures which capture "Feature" (lambdas in the feature are not
-    // in this lambda group).
-    assertTrue(
-        invokeMethod
-            .streamInstructions()
-            .filter(InstructionSubject::isCheckCast)
-            .noneMatch(
-                instruction ->
-                    instruction.asCheckCast().getType().toSourceString().contains("Feature")));
-  }
-
   @Test
   public void test() throws Exception {
     Path featureCode = temp.newFile("feature.zip").toPath();
@@ -125,10 +82,16 @@
             .addKeepClassAndMembersRules(baseClassName)
             .addKeepClassAndMembersRules(featureKtClassNamet)
             .addKeepClassAndMembersRules(FeatureAPI.class)
-            .addOptionsModification(
-                options -> options.horizontalClassMergerOptions().disableKotlinLambdaMerging())
+            .addHorizontallyMergedClassesInspector(
+                inspector ->
+                    inspector
+                        .assertIsCompleteMergeGroup(
+                            "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt$main$1",
+                            "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt$main$2")
+                        .assertIsCompleteMergeGroup(
+                            "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt$feature$1",
+                            "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt$feature$2"))
             .setMinApi(parameters.getApiLevel())
-            .noMinification() // The check cast inspection above relies on original names.
             .addFeatureSplit(
                 builder ->
                     builder
@@ -142,10 +105,9 @@
             .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .assertAllWarningMessagesMatch(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
-            .inspect(this::checkLambdaGroups);
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."));
 
-    // Run the code without the feature code present.
+    // Run the code without the feature code.
     compileResult
         .run(parameters.getRuntime(), baseKtClassName)
         .assertSuccessWithOutputLines("1", "2");
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index c33d03b..8d476be 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -11,21 +11,19 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,36 +32,61 @@
 @RunWith(Parameterized.class)
 public class LambdaGroupGCLimitTest extends TestBase {
 
-  private final boolean enableHorizontalClassMergingOfKotlinLambdas;
-  private final TestParameters parameters;
-  private final int LAMBDA_HOLDER_LIMIT = 50;
-  private final int LAMBDAS_PER_CLASS_LIMIT = 100;
+  private static final int LAMBDA_HOLDER_LIMIT = 50;
+  private static final int LAMBDAS_PER_CLASS_LIMIT = 100;
 
-  @Parameters(name = "{1}, horizontal class merging: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
-  public LambdaGroupGCLimitTest(
-      boolean enableHorizontalClassMergingOfKotlinLambdas, TestParameters parameters) {
-    this.enableHorizontalClassMergingOfKotlinLambdas = enableHorizontalClassMergingOfKotlinLambdas;
+  public LambdaGroupGCLimitTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
   @Test
   public void testR8() throws ExecutionException, CompilationFailedException, IOException {
     String PKG_NAME = LambdaGroupGCLimitTest.class.getPackage().getName();
-    R8FullTestBuilder testBuilder =
+    R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
-            .addProgramFiles(ToolHelper.getKotlinStdlibJar(ToolHelper.getKotlinC_1_3_72()))
-            .addOptionsModification(
-                options ->
-                    options
-                        .horizontalClassMergerOptions()
-                        .enableKotlinLambdaMergingIf(enableHorizontalClassMergingOfKotlinLambdas))
+            .addProgramFiles(getProgramFiles())
             .setMinApi(parameters.getApiLevel())
-            .noMinification();
+            .apply(
+                builder -> {
+                  for (int mainId = 0; mainId < LAMBDA_HOLDER_LIMIT; mainId++) {
+                    builder.addKeepClassAndMembersRules(PKG_NAME + ".MainKt" + mainId);
+                  }
+                })
+            .addDontWarnJetBrainsNotNullAnnotation()
+            .addHorizontallyMergedClassesInspector(
+                inspector -> {
+                  HorizontalClassMergerOptions defaultHorizontalClassMergerOptions =
+                      new HorizontalClassMergerOptions();
+                  assertEquals(4833, inspector.getSources().size());
+                  assertEquals(167, inspector.getTargets().size());
+                  assertTrue(
+                      inspector.getMergeGroups().stream()
+                          .allMatch(
+                              mergeGroup ->
+                                  mergeGroup.size()
+                                      <= defaultHorizontalClassMergerOptions.getMaxGroupSize()));
+                })
+            .compile();
+    Path path = compileResult.writeToZip();
+    compileResult
+        .run(parameters.getRuntime(), PKG_NAME + ".MainKt0")
+        .assertSuccessWithOutputLines("3");
+    Path oatFile = temp.newFile("out.oat").toPath();
+    ProcessResult processResult =
+        ToolHelper.runDex2OatRaw(path, oatFile, parameters.getRuntime().asDex().getVm());
+    assertEquals(0, processResult.exitCode);
+    assertThat(
+        processResult.stderr, not(containsString("Method exceeds compiler instruction limit")));
+  }
+
+  private List<Path> getProgramFiles() throws IOException {
     Path classFiles = temp.newFile("classes.jar").toPath();
     List<byte[]> classFileData = new ArrayList<>();
     for (int mainId = 0; mainId < LAMBDA_HOLDER_LIMIT; mainId++) {
@@ -71,50 +94,9 @@
       for (int lambdaId = 0; lambdaId < LAMBDAS_PER_CLASS_LIMIT; lambdaId++) {
         classFileData.add(MainKt$main$1Dump.dump(mainId, lambdaId));
       }
-      testBuilder.addKeepClassAndMembersRules(PKG_NAME + ".MainKt" + mainId);
     }
     writeClassFileDataToJar(classFiles, classFileData);
-    R8TestCompileResult compileResult =
-        testBuilder
-            .addProgramFiles(classFiles)
-            .addHorizontallyMergedClassesInspector(
-                inspector -> {
-                  if (enableHorizontalClassMergingOfKotlinLambdas) {
-                    HorizontalClassMergerOptions defaultHorizontalClassMergerOptions =
-                        new HorizontalClassMergerOptions();
-                    assertEquals(4833, inspector.getSources().size());
-                    assertEquals(167, inspector.getTargets().size());
-                    assertTrue(
-                        inspector.getMergeGroups().stream()
-                            .allMatch(
-                                mergeGroup ->
-                                    mergeGroup.size()
-                                        <= defaultHorizontalClassMergerOptions.getMaxGroupSize()));
-                  } else {
-                    inspector.assertNoClassesMerged();
-                  }
-                })
-            .addDontWarnJetBrainsNotNullAnnotation()
-            .compile();
-    Path path = compileResult.writeToZip();
-    compileResult
-        .run(parameters.getRuntime(), PKG_NAME + ".MainKt0")
-        .assertSuccessWithOutputLines("3")
-        .inspect(
-            codeInspector -> {
-              List<FoundClassSubject> lambdaGroups =
-                  codeInspector.allClasses().stream()
-                      .filter(c -> c.getFinalName().contains("LambdaGroup"))
-                      .collect(Collectors.toList());
-              assertEquals(
-                  1 - BooleanUtils.intValue(enableHorizontalClassMergingOfKotlinLambdas),
-                  lambdaGroups.size());
-            });
-    Path oatFile = temp.newFile("out.oat").toPath();
-    ProcessResult processResult =
-        ToolHelper.runDex2OatRaw(path, oatFile, parameters.getRuntime().asDex().getVm());
-    assertEquals(0, processResult.exitCode);
-    assertThat(
-        processResult.stderr, not(containsString("Method exceeds compiler instruction limit")));
+    return ImmutableList.of(
+        classFiles, ToolHelper.getKotlinStdlibJar(ToolHelper.getKotlinC_1_3_72()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
index 663c081..79cef64 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
@@ -4,24 +4,25 @@
 
 package com.android.tools.r8.kotlin.lambda.b159688129;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.List;
-import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,26 +32,20 @@
 public class LambdaSplitByCodeCorrectnessTest extends AbstractR8KotlinTestBase {
 
   private final TestParameters parameters;
-  private final KotlinTargetVersion targetVersion;
   private final boolean splitGroup;
 
-  @Parameters(name = "{0}, kotlinc: {2} targetVersion: {1}, splitGroup: {3}")
+  @Parameters(name = "{0}, {1}, splitGroup: {2}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withDexRuntimes().withAllApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public LambdaSplitByCodeCorrectnessTest(
-      TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
-      boolean splitGroup) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters, boolean splitGroup) {
+    super(kotlinParameters);
     this.parameters = parameters;
-    this.targetVersion = targetVersion;
     this.splitGroup = splitGroup;
   }
 
@@ -67,8 +62,6 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addProgramFiles(ktClasses)
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().disableKotlinLambdaMerging())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(PKG_NAME + ".SimpleKt")
         .addDontWarnJetBrainsNotNullAnnotation()
@@ -77,25 +70,40 @@
             b ->
                 b.addOptionsModification(
                     internalOptions ->
-                        // Setting verificationSizeLimitInBytesOverride = 1 will force a a chain
-                        // having only a single implementation method in each.
-                        internalOptions.testing.verificationSizeLimitInBytesOverride =
-                            splitGroup ? 1 : -1))
-        .noMinification()
+                        // Setting inliningInstructionAllowance = 1 will force each switch branch to
+                        // contain an invoke instruction to a private method.
+                        internalOptions.inliningInstructionAllowance = 1))
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(
+                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$1",
+                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$2",
+                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$3",
+                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$4",
+                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$5",
+                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$6"))
         .allowDiagnosticWarningMessages()
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .inspect(
             codeInspector -> {
-              List<FoundClassSubject> lambdaGroups =
-                  codeInspector.allClasses().stream()
-                      .filter(c -> c.getFinalName().contains("LambdaGroup"))
-                      .collect(Collectors.toList());
-              assertEquals(1, lambdaGroups.size());
-              FoundClassSubject lambdaGroup = lambdaGroups.get(0);
-              List<FoundMethodSubject> invokeChain =
-                  lambdaGroup.allMethods(method -> method.getFinalName().contains("invoke$"));
-              assertEquals(splitGroup ? 5 : 0, invokeChain.size());
+              ClassSubject mergeTarget =
+                  codeInspector.clazz(
+                      "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$1");
+              assertThat(mergeTarget, isPresent());
+
+              MethodSubject virtualMethodSubject =
+                  mergeTarget.uniqueMethodThatMatches(
+                      method -> method.isVirtual() && !method.isSynthetic());
+              assertThat(virtualMethodSubject, isPresent());
+
+              int found = 0;
+              for (FoundMethodSubject directMethodSubject :
+                  mergeTarget.allMethods(x -> x.isPrivate() && !x.isSynthetic())) {
+                assertThat(virtualMethodSubject, invokesMethod(directMethodSubject));
+                found++;
+              }
+              assertEquals(splitGroup ? 6 : 0, found);
             })
         .run(parameters.getRuntime(), PKG_NAME + ".SimpleKt")
         .assertSuccessWithOutputLines("Hello1", "Hello2", "Hello3", "Hello4", "Hello5", "Hello6");
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index e158fc7..3765187 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -8,8 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNull;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -22,8 +21,8 @@
 
 public abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
 
-  public KotlinMetadataTestBase(KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+  public KotlinMetadataTestBase(KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
   }
 
   static final String PKG = KotlinMetadataTestBase.class.getPackage().getName();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
index b4fd614..b0a30d7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
@@ -4,17 +4,15 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.JvmTestRunResult;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -32,17 +30,16 @@
   private static final String PKG_LIB = PKG + ".primitive_type_rewrite_lib";
   private static final String PKG_APP = PKG + ".primitive_type_rewrite_app";
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, compiler: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataPrimitiveTypeRewriteTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java
index a048351..09c2a9d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrunedFieldsTest.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.metadata.metadata_pruned_fields.Main;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -28,17 +26,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataPrunedFieldsTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
index b6d7701..71bd275 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -52,17 +50,16 @@
           "staticPrivate",
           "staticInternal");
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteAllowAccessModificationTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
index a285149..ebe0dac 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
@@ -4,17 +4,15 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -58,17 +56,16 @@
   private static final String FOO_ORIGINAL_NAME = PKG_LIB + ".Foo";
   private static final String FOO_FINAL_NAME = "a.b.c";
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteAnnotationTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
index 8db1944..374c3fd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
@@ -4,14 +4,12 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -27,17 +25,16 @@
 
   private final String EXPECTED = "foo";
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteAnonymousTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
index 4e7d224..16d3765 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
@@ -4,17 +4,15 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNotNull;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNull;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
@@ -37,17 +35,16 @@
   private final String EXPECTED =
       StringUtils.lines("false", "0", "a", "0.042", "0.42", "42", "442", "1", "2", "42", "42");
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteBoxedTypesTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
index 065d63f..989512b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -4,12 +4,9 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
@@ -25,17 +22,16 @@
   private static final String PKG_LIB = PKG + ".crossinline_anon_lib";
   private static final String PKG_APP = PKG + ".crossinline_anon_app";
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteCrossinlineAnonFunctionTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
index 15f68ea..d969f34 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
@@ -4,12 +4,9 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
@@ -25,17 +22,16 @@
   private static final String PKG_LIB = PKG + ".crossinline_concrete_lib";
   private static final String PKG_APP = PKG + ".crossinline_concrete_app";
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteCrossinlineConcreteFunctionTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index b43f846..bc8051d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -4,12 +4,9 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -33,17 +30,16 @@
           "null",
           "New value has been read in CustomDelegate from 'x'");
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteDelegatedPropertyTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
index 2ccbb3a..3f42968 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
@@ -4,15 +4,13 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -27,19 +25,18 @@
 @RunWith(Parameterized.class)
 public class MetadataRewriteDependentKeep extends KotlinMetadataTestBase {
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   private final TestParameters parameters;
 
   public MetadataRewriteDependentKeep(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
index feca0f8..1caec7d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
@@ -4,14 +4,12 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
@@ -29,19 +27,18 @@
 
   private final Set<String> nullableFieldKeys = Sets.newHashSet("pn", "xs", "xi");
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   private final TestParameters parameters;
 
   public MetadataRewriteDoNotEmitValuesIfEmpty(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
index fbfc10f..91ede26 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
@@ -12,10 +11,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -39,17 +37,16 @@
   private final String EXPECTED = StringUtils.lines("B.foo(): 42");
   private final String PKG_LIB = PKG + ".flexible_upper_bound_lib";
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteFlexibleUpperBoundTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index c7b9ea6..eebb77f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -11,10 +10,9 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -35,17 +33,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInClasspathTypeTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index bdcb905..9106a3b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
@@ -12,10 +11,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -44,17 +42,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInCompanionTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index f9124bd..f46402b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -13,10 +12,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -41,17 +39,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInExtensionFunctionTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index c78cea1..c2a923f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -14,10 +13,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -40,17 +38,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInExtensionPropertyTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index 5c8468d..48044e2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -13,10 +12,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -39,17 +37,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0} target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInFunctionTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
index db348d0..746c690 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -12,10 +11,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -39,17 +37,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInFunctionWithDefaultValueTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
index 65207dd..262d8a2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -13,10 +12,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -40,17 +38,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInFunctionWithVarargTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index f3c59d9..d583ea7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static org.hamcrest.CoreMatchers.anyOf;
@@ -11,10 +10,9 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -32,17 +30,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInLibraryTypeTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 3949d6e..6411cfc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -16,10 +15,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -44,17 +42,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInMultifileClassTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
index 8bb4603..f2aa77d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -12,10 +11,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -34,17 +32,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInNestedClassTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
index f84fa48..abce684 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
@@ -3,17 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -32,17 +30,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInParameterTypeTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index af4c9b6..693c7fc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -14,10 +13,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -38,17 +36,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInPropertyTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
index 0113f19..cb18acb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
@@ -3,17 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -32,17 +30,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInPropertyTypeTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
index a4a3a68..7badf74 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKotlinClassifier;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -12,10 +11,9 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -33,17 +31,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInRenamedTypeTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
index c8144f4..dbaf933 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
@@ -3,17 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -32,17 +30,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInReturnTypeTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
index db927a8..e479bba 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
@@ -3,12 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -31,17 +28,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInSealedClassNestedTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index ef43f6e..5156b72 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -15,10 +14,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
@@ -39,17 +37,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInSealedClassTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
index 9b0da43..ccc94e5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
@@ -11,10 +10,9 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -33,17 +31,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInSuperTypeTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index e680d6e..98102c5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isDexClass;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
@@ -13,10 +12,9 @@
 import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -61,17 +59,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInTypeAliasTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index acdda5b..097c832 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isDexClass;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -12,10 +11,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -77,17 +75,16 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInTypeArgumentsTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
index ac232d7..d5e9e1a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
@@ -4,15 +4,13 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNull;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
@@ -34,17 +32,16 @@
 
   private final String EXPECTED = StringUtils.lines("true", "false", "false", "true");
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewriteInlinePropertyTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
index 0558ebc..e03a72c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 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.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
@@ -40,12 +39,18 @@
 
   @Parameterized.Parameters(name = "{0}, kotlinc: {1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withCfRuntimes().build(), getKotlinCompilers());
+    // We are testing static methods on interfaces which requires java 8.
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(),
+        getKotlinTestParameters()
+            .withAllCompilers()
+            .withTargetVersion(KotlinTargetVersion.JAVA_8)
+            .build());
   }
 
-  public MetadataRewriteJvmStaticTest(TestParameters parameters, KotlinCompiler kotlinc) {
-    // We are testing static methods on interfaces which requires java 8.
-    super(KotlinTargetVersion.JAVA_8, kotlinc);
+  public MetadataRewriteJvmStaticTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
index 056b18b..569d2b7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
@@ -4,17 +4,15 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -26,12 +24,11 @@
 @RunWith(Parameterized.class)
 public class MetadataRewriteKeepPathTest extends KotlinMetadataTestBase {
 
-  @Parameterized.Parameters(name = "{0} target: {1}, kotlinc: {2}, keep: {3}")
+  @Parameterized.Parameters(name = "{0}, {1}, keep: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
@@ -42,11 +39,8 @@
   private final boolean keepMetadata;
 
   public MetadataRewriteKeepPathTest(
-      TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
-      boolean keepMetadata) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters, boolean keepMetadata) {
+    super(kotlinParameters);
     this.parameters = parameters;
     this.keepMetadata = keepMetadata;
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
index 74159a6..c8dd873 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -4,14 +4,12 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -23,19 +21,17 @@
 @RunWith(Parameterized.class)
 public class MetadataRewriteKeepTest extends KotlinMetadataTestBase {
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   private final TestParameters parameters;
 
-  public MetadataRewriteKeepTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+  public MetadataRewriteKeepTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 0b65ec3..99586b1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -4,12 +4,9 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
-
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.Collection;
@@ -20,19 +17,18 @@
 @RunWith(Parameterized.class)
 public class MetadataRewritePassThroughTest extends KotlinMetadataTestBase {
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   private final TestParameters parameters;
 
   public MetadataRewritePassThroughTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
index 3d3386c..6d4391f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -37,17 +35,16 @@
           getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"));
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withCfRuntimes().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public MetadataRewritePrunedObjectsTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 1a2cd73..8111567 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
@@ -11,11 +10,10 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -31,17 +29,15 @@
   private final TestParameters parameters;
   private static final String FOLDER = "lambdas_jstyle_runnable";
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
-  public MetadataStripTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+  public MetadataStripTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
index e23ac8f..a7886b2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
@@ -9,9 +9,9 @@
 import static org.junit.Assert.fail;
 import static org.objectweb.asm.Opcodes.ASM7;
 
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.junit.Test;
@@ -39,12 +40,18 @@
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getKotlinTestParameters()
+            .withCompiler(getKotlinC_1_3_72())
+            .withTargetVersion(KotlinTargetVersion.JAVA_8)
+            .build());
   }
 
-  public MetadataVersionNumberBumpTest(TestParameters parameters) {
-    super(KotlinTargetVersion.JAVA_8, getKotlinC_1_3_72());
+  public MetadataVersionNumberBumpTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index ac7eb23..ae80118 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -4,16 +4,14 @@
 
 package com.android.tools.r8.kotlin.reflection;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -29,7 +27,6 @@
 public class KotlinReflectTest extends KotlinTestBase {
 
   private final TestParameters parameters;
-  private final KotlinTargetVersion targetVersion;
   private static final String EXPECTED_OUTPUT = "Hello World!";
   private static final String PKG = KotlinReflectTest.class.getPackage().getName();
   private static final KotlinCompileMemoizer compiledJars =
@@ -40,19 +37,16 @@
               DescriptorUtils.getBinaryNameFromJavaType(PKG),
               "SimpleReflect" + FileUtils.KT_EXTENSION));
 
-  @Parameters(name = "{0}, target: {1}, kotlinc: {2}")
+  @Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
-  public KotlinReflectTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+  public KotlinReflectTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
-    this.targetVersion = targetVersion;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
index 92b6b97..47f9463 100644
--- a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
@@ -6,15 +6,13 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -33,17 +31,15 @@
 
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
-  public SealedClassTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+  public SealedClassTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
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()));
         });
   }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
index 7041c75..9451f9a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
@@ -4,17 +4,13 @@
 
 package com.android.tools.r8.maindexlist;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilation;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assume.assumeTrue;
-
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -27,7 +23,10 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
   }
 
   public MainDexListMergeInRootTest(TestParameters parameters) {
@@ -35,35 +34,26 @@
   }
 
   @Test
-  public void testMainDexTracing() {
-    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
-    assertFailsCompilation(
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClasses(OutsideMainDex.class, InsideA.class, InsideB.class, Main.class)
-                .addKeepClassAndMembersRules(Main.class)
-                .setMinApi(parameters.getApiLevel())
-                .enableNeverClassInliningAnnotations()
-                .enableNoHorizontalClassMergingAnnotations()
-                .enableInliningAnnotations()
-                .noMinification()
-                .addMainDexRules(
-                    "-keep class "
-                        + Main.class.getTypeName()
-                        + " { public static void main(***); }")
-                .addOptionsModification(
-                    options -> {
-                      options.testing.checkForNotExpandingMainDexTracingResult = true;
-                    })
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertErrorsMatch(
-                          diagnosticMessage(
-                              containsString(
-                                  "Class com.android.tools.r8.maindexlist"
-                                      + ".MainDexListMergeInRootTest$OutsideMainDex"
-                                      + " was not a main dex root in the first round")));
-                    }));
+  public void testMainDexTracing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(OutsideMainDex.class, InsideA.class, InsideB.class, Main.class)
+        .addKeepClassAndMembersRules(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableInliningAnnotations()
+        .noMinification()
+        .addMainDexRules(
+            "-keep class " + Main.class.getTypeName() + " { public static void main(***); }")
+        .addOptionsModification(
+            options -> {
+              options.testing.checkForNotExpandingMainDexTracingResult = true;
+            })
+        // TODO(b/178151906): See if we can merge the classes.
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("InsideB::live0", "InsideA::live");
   }
 
   @NoHorizontalClassMerging
@@ -80,7 +70,7 @@
   public static class InsideA {
 
     public void bar() {
-      System.out.println("A::live");
+      System.out.println("InsideA::live");
     }
 
     /* Not a traced root */
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
index 65bf904..e2d7dd2 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
@@ -6,8 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
@@ -20,6 +19,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableSet;
 import java.util.Set;
 import java.util.function.Consumer;
 import org.junit.Assert;
@@ -53,10 +53,7 @@
     assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
     testMainDex(
         builder -> builder.addMainDexListClasses(Main.class),
-        mainDexClasses -> {
-          assertTrue(mainDexClasses.contains(Main.class.getTypeName()));
-          assertFalse(mainDexClasses.contains(Outside.class.getTypeName()));
-        });
+        mainDexClasses -> assertEquals(ImmutableSet.of(Main.class.getTypeName()), mainDexClasses));
   }
 
   @Test
@@ -66,11 +63,7 @@
         builder ->
             builder.addMainDexRules(
                 "-keep class " + Main.class.getTypeName() + " { public static void notMain(); }"),
-        mainDexClasses -> {
-          assertTrue(mainDexClasses.contains(Main.class.getTypeName()));
-          // TODO(b/178362682): This should be false.
-          assertTrue(mainDexClasses.contains(Outside.class.getTypeName()));
-        });
+        mainDexClasses -> assertEquals(ImmutableSet.of(Main.class.getTypeName()), mainDexClasses));
   }
 
   private void testMainDex(
@@ -89,7 +82,7 @@
             parameters.getDexRuntimeVersion().isDalvik(),
             TestCompilerBuilder::collectMainDexClasses)
         .compile()
-        .apply(compileResult -> compileResult.inspectMainDexClasses(mainDexListConsumer))
+        .inspectMainDexClasses(mainDexListConsumer)
         .run(parameters.getRuntime(), Main.class)
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRootIfRuleTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRootIfRuleTest.java
new file mode 100644
index 0000000..4520900
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexRootIfRuleTest.java
@@ -0,0 +1,81 @@
+// 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 org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableSet;
+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 MainDexRootIfRuleTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexRootIfRuleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(R.class)
+        .addMainDexRules(
+            "-keep class " + Main.class.getTypeName() + " { void main(java.lang.String[]); }")
+        .addMainDexRules(
+            "-if class " + MainDexRoot.class.getTypeName() + " { void methodWithSingleCaller(); }",
+            "-keep class " + R.class.getTypeName())
+        .collectMainDexClasses()
+        .compile()
+        .inspectMainDexClasses(
+            mainDexClasses -> {
+              // TODO(b/164019179): Fix if-rules
+              // MainDexRoot will be traced during first round of main dex tracing and result in the
+              // R class being kept due to the if-rule. When tracing in the second round, the
+              // class MainDexRoot is gone and the method inlined, resulting in R not being added to
+              // Main Dex.
+              assertEquals(ImmutableSet.of(Main.class.getTypeName()), mainDexClasses);
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(R.class.getName());
+  }
+
+  public static class R {}
+
+  public static class MainDexRoot {
+
+    public static void methodWithSingleCaller() throws Exception {
+      // Reflectively access class R
+      String[] strings =
+          new String[] {"com", "android", "tools", "r8", "maindexlist", "MainDexRootIfRuleTest$R"};
+      Class<?> clazz = Class.forName(String.join(".", strings));
+      System.out.println(clazz.getName());
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      MainDexRoot.methodWithSingleCaller();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index ccaa66a..bcea2fe 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.references.Reference;
@@ -212,11 +211,7 @@
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
         AndroidApiLevel.I,
-        builder ->
-            builder
-                .applyIf(
-                    backend.isDex(), TestShrinkerBuilder::addDontWarnCompilerSynthesizedAnnotations)
-                .addOptionsModification(options -> options.enableInlining = false));
+        builder -> builder.addOptionsModification(options -> options.enableInlining = false));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java
new file mode 100644
index 0000000..fea8bfa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java
@@ -0,0 +1,115 @@
+// 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.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+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 MainDexUnusedArgumentRewriteWithLensTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexUnusedArgumentRewriteWithLensTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Box<Set<String>> mainDex = new Box<>();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassRules(Dependency.class)
+        .addMainDexRules(
+            "-keep class " + A.class.getTypeName() + " { void foo(java.lang.Object,int); }")
+        .addKeepMainRule(Main.class)
+        .collectMainDexClasses()
+        .compile()
+        .inspectMainDexClasses(mainDex::set)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A::foo")
+        .inspect(
+            inspector -> {
+              ClassSubject aSubject = inspector.clazz(A.class);
+              assertThat(aSubject, isPresent());
+              ClassSubject bSubject = inspector.clazz(B.class);
+              assertThat(bSubject, isPresent());
+              ClassSubject mainSubject = inspector.clazz(Main.class);
+              assertThat(mainSubject, isPresentAndNotRenamed());
+              MethodSubject mainMethodSubject = mainSubject.uniqueMethodWithName("main");
+              assertThat(mainMethodSubject, isPresentAndNotRenamed());
+              assertTrue(
+                  mainMethodSubject
+                      .streamInstructions()
+                      .anyMatch(
+                          instructionSubject ->
+                              instructionSubject.isNewInstance(Dependency.class.getTypeName())));
+              assertEquals(
+                  mainDex.get(),
+                  ImmutableSet.of(
+                      aSubject.getFinalName(),
+                      bSubject.getFinalName(),
+                      Dependency.class.getTypeName()));
+            });
+  }
+
+  public static class Dependency {}
+
+  public static class B {
+
+    @NeverInline
+    public static void foo(Object obj) {
+      if (!(obj instanceof Dependency)) {
+        System.out.println("A::foo");
+      }
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+
+    // Will be rewritten because it has an unused argument
+    @NeverInline
+    public void foo(Object obj, int argumentUnused) {
+      B.foo(obj);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo(args.length > 0 ? new Dependency() : new Object(), 42);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCatchHandlerTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCatchHandlerTest.java
new file mode 100644
index 0000000..e9bc43b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCatchHandlerTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+public class MissingClassReferencedFromCatchHandlerTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromCatchHandlerTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      try {
+        nop();
+      } catch (MissingClass ignore) {
+      }
+    }
+
+    private static void nop() {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCheckCastTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCheckCastTest.java
new file mode 100644
index 0000000..b10a231
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromCheckCastTest.java
@@ -0,0 +1,56 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+public class MissingClassReferencedFromCheckCastTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromCheckCastTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      MissingClass ignore = (MissingClass) null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromConstClassTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromConstClassTest.java
new file mode 100644
index 0000000..cb6287c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromConstClassTest.java
@@ -0,0 +1,56 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+public class MissingClassReferencedFromConstClassTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromConstClassTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Class<?> ignore = MissingClass.class;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromEnclosingMethodAttributeTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromEnclosingMethodAttributeTest.java
new file mode 100644
index 0000000..74f00d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromEnclosingMethodAttributeTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class MissingClassReferencedFromEnclosingMethodAttributeTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(getMainClass());
+
+  @Parameters(name = "{1}, report: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  private final boolean reportMissingClassesInEnclosingMethodAttribute;
+
+  public MissingClassReferencedFromEnclosingMethodAttributeTest(
+      boolean reportMissingClassesInEnclosingMethodAttribute, TestParameters parameters) {
+    super(parameters);
+    this.reportMissingClassesInEnclosingMethodAttribute =
+        reportMissingClassesInEnclosingMethodAttribute;
+  }
+
+  @Test
+  public void testNoRules() throws Exception {
+    assertFailsCompilationIf(
+        reportMissingClassesInEnclosingMethodAttribute,
+        () ->
+            compileWithExpectedDiagnostics(
+                getMainClass(),
+                reportMissingClassesInEnclosingMethodAttribute
+                    ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+                    : TestDiagnosticMessages::assertNoMessages,
+                this::configure));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        getMainClass(),
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(getMainClass()).andThen(this::configure));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        getMainClass(),
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(this::configure));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        getMainClass(),
+        reportMissingClassesInEnclosingMethodAttribute
+            ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+            : TestDiagnosticMessages::assertNoMessages,
+        addIgnoreWarnings(reportMissingClassesInEnclosingMethodAttribute).andThen(this::configure));
+  }
+
+  void configure(R8FullTestBuilder builder) {
+    builder
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addOptionsModification(
+            options -> {
+              // We do not report missing classes from inner class attributes by default.
+              assertFalse(options.reportMissingClassesInEnclosingMethodAttribute);
+              options.reportMissingClassesInEnclosingMethodAttribute =
+                  reportMissingClassesInEnclosingMethodAttribute;
+            })
+        .applyIf(
+            !reportMissingClassesInEnclosingMethodAttribute,
+            // The -dontwarn Main and -dontwarn MissingClass tests will have unused -dontwarn rules.
+            R8TestBuilder::allowUnusedDontWarnPatterns);
+  }
+
+  static Class<?> getMainClass() {
+    return MissingClass.getMainClass();
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingClass.class);
+  }
+
+  static class MissingClass {
+
+    static Class<?> getMainClass() {
+      class Main {}
+      return Main.class;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromImplementsClauseTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromImplementsClauseTest.java
new file mode 100644
index 0000000..490efdc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromImplementsClauseTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+public class MissingClassReferencedFromImplementsClauseTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromImplementsClauseTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingInterface.class);
+  }
+
+  // The tests explicitly disable desugaring to prevent desugaring warnings.
+  // TODO(b/179341237): Consider if desugaring warnings should be D8 only, since in a way they are
+  //  all duplicates of the MissingClassesDiagnostic.
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        TestCompilerBuilder::disableDesugaring);
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(TestCompilerBuilder::disableDesugaring));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingInterface.class).andThen(TestCompilerBuilder::disableDesugaring));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings().andThen(TestCompilerBuilder::disableDesugaring));
+  }
+
+  static class Main implements MissingInterface {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInnerClassAttributeTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInnerClassAttributeTest.java
new file mode 100644
index 0000000..aa64ab8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInnerClassAttributeTest.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class MissingClassReferencedFromInnerClassAttributeTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  @Parameters(name = "{1}, report: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  private final boolean reportMissingClassesInInnerClassAttributes;
+
+  public MissingClassReferencedFromInnerClassAttributeTest(
+      boolean reportMissingClassesInInnerClassAttributes, TestParameters parameters) {
+    super(parameters);
+    this.reportMissingClassesInInnerClassAttributes = reportMissingClassesInInnerClassAttributes;
+  }
+
+  @Test
+  public void testNoRules() throws Exception {
+    assertFailsCompilationIf(
+        reportMissingClassesInInnerClassAttributes,
+        () ->
+            compileWithExpectedDiagnostics(
+                Main.class,
+                reportMissingClassesInInnerClassAttributes
+                    ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+                    : TestDiagnosticMessages::assertNoMessages,
+                this::configure));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(this::configure));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.MissingClass.class).andThen(this::configure));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        reportMissingClassesInInnerClassAttributes
+            ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+            : TestDiagnosticMessages::assertNoMessages,
+        addIgnoreWarnings(reportMissingClassesInInnerClassAttributes).andThen(this::configure));
+  }
+
+  void configure(R8FullTestBuilder builder) {
+    builder
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addOptionsModification(
+            options -> {
+              // We do not report missing classes from inner class attributes by default.
+              assertFalse(options.reportMissingClassesInInnerClassAttributes);
+              options.reportMissingClassesInInnerClassAttributes =
+                  reportMissingClassesInInnerClassAttributes;
+            })
+        .applyIf(
+            reportMissingClassesInInnerClassAttributes,
+            // We need to ignore the inner class attribute for the test class.
+            addDontWarn(MissingClassReferencedFromInnerClassAttributeTest.class),
+            // The -dontwarn Main and -dontwarn MissingClass tests will have unused -dontwarn rules.
+            R8TestBuilder::allowUnusedDontWarnPatterns);
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(Main.MissingClass.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+
+    static class MissingClass {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToExistingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToExistingFieldTest.java
new file mode 100644
index 0000000..6c7f620
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToExistingFieldTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class resolves to a definition, then the field
+ * definition is to be blamed.
+ */
+public class MissingClassReferencedFromInstanceGetToExistingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(
+          Reference.classFromClass(Main.class),
+          "field",
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromInstanceGetToExistingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public MissingClass field;
+
+    public static void main(String[] args) {
+      MissingClass ignore = new Main().field;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToMissingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToMissingFieldTest.java
new file mode 100644
index 0000000..851d65d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceGetToMissingFieldTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class does not resolve, then the enclosing method
+ * is to be blamed.
+ */
+public class MissingClassReferencedFromInstanceGetToMissingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromInstanceGetToMissingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int ignore = new MissingClass().field;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceOfTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceOfTest.java
new file mode 100644
index 0000000..2d05aac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstanceOfTest.java
@@ -0,0 +1,56 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+public class MissingClassReferencedFromInstanceOfTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromInstanceOfTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      boolean ignore = null instanceof MissingClass;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToExistingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToExistingFieldTest.java
new file mode 100644
index 0000000..b69625e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToExistingFieldTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class resolves to a definition, then the field
+ * definition is to be blamed.
+ */
+public class MissingClassReferencedFromInstancePutToExistingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(
+          Reference.classFromClass(Main.class),
+          "field",
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromInstancePutToExistingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    MissingClass field;
+
+    public static void main(String[] args) {
+      new Main().field = null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToMissingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToMissingFieldTest.java
new file mode 100644
index 0000000..ec615f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInstancePutToMissingFieldTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class does not resolve, then the enclosing method
+ * is to be blamed.
+ */
+public class MissingClassReferencedFromInstancePutToMissingFieldTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromInstancePutToMissingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new MissingClass().field = 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptFieldTest.java
new file mode 100644
index 0000000..19dfdea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptFieldTest.java
@@ -0,0 +1,73 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+/** If a field definition refers to a missing class, then the field definition is to be blamed. */
+public class MissingClassReferencedFromKeptFieldTest extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(
+          Reference.classFromClass(Main.class),
+          "FIELD",
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromKeptFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        this::addKeepFieldRule);
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(this::addKeepFieldRule));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(this::addKeepFieldRule));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings().andThen(this::addKeepFieldRule));
+  }
+
+  private void addKeepFieldRule(R8FullTestBuilder builder) {
+    builder.addKeepRules(
+        "-keep class " + Main.class.getTypeName() + " {",
+        "  public static " + MissingClass.class.getTypeName() + " FIELD;",
+        "}");
+  }
+
+  static class Main {
+
+    public static MissingClass FIELD;
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNestHostAttributeTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNestHostAttributeTest.java
new file mode 100644
index 0000000..fc1ac99
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNestHostAttributeTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.missingclasses.MissingClassReferencedFromNestHostAttributeTest.MissingClass.Main;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Collection;
+import org.junit.Test;
+
+public class MissingClassReferencedFromNestHostAttributeTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromNestHostAttributeTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain(), diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(Main.class)), TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(MissingClass.class)),
+        TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addIgnoreWarnings()),
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom));
+  }
+
+  private ThrowableConsumer<R8FullTestBuilder> addMain() {
+    return builder ->
+        builder.addProgramClassFileData(getProgramClassFileData()).addKeepMainRule(Main.class);
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingClass.class);
+  }
+
+  static Collection<byte[]> getProgramClassFileData() throws IOException {
+    return ImmutableList.of(
+        transformer(Main.class).setNest(MissingClass.class, Main.class).transform());
+  }
+
+  static /*host*/ class MissingClass {
+
+    static /*member*/ class Main {
+
+      public static void main(String[] args) {}
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNestMemberAttributeTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNestMemberAttributeTest.java
new file mode 100644
index 0000000..a151dce
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNestMemberAttributeTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Collection;
+import org.junit.Test;
+
+public class MissingClassReferencedFromNestMemberAttributeTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromNestMemberAttributeTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain(), diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(Main.class)), TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(Main.MissingClass.class)),
+        TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addIgnoreWarnings()),
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom));
+  }
+
+  private ThrowableConsumer<R8FullTestBuilder> addMain() {
+    return builder ->
+        builder.addProgramClassFileData(getProgramClassFileData()).addKeepMainRule(Main.class);
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(Main.MissingClass.class);
+  }
+
+  static Collection<byte[]> getProgramClassFileData() throws IOException {
+    return ImmutableList.of(
+        transformer(Main.class).setNest(Main.class, Main.MissingClass.class).transform());
+  }
+
+  static /*host*/ class Main {
+
+    static /*member*/ class MissingClass {}
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewArrayTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewArrayTest.java
new file mode 100644
index 0000000..66242bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewArrayTest.java
@@ -0,0 +1,56 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+public class MissingClassReferencedFromNewArrayTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromNewArrayTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      MissingClass[] ignore = new MissingClass[0];
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
index 8f43ab8..2af5045 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
@@ -4,51 +4,47 @@
 
 package com.android.tools.r8.missingclasses;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static org.junit.Assert.assertEquals;
-
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.shaking.MissingClassesDiagnostic;
-import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
 import org.junit.Test;
 
 public class MissingClassReferencedFromNewInstanceTest extends MissingClassesTestBase {
 
-  public MissingClassReferencedFromNewInstanceTest(
-      DontWarnConfiguration dontWarnConfiguration, TestParameters parameters) {
-    super(dontWarnConfiguration, parameters);
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromNewInstanceTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
   }
 
   @Test
-  public void test() throws Exception {
-    AssertUtils.assertFailsCompilationIf(
-        // TODO(b/175542052): Should not fail compilation with -dontwarn Main.
-        !getDontWarnConfiguration().isDontWarnMissingClass(),
-        () ->
-            compileWithExpectedDiagnostics(
-                Main.class, MissingClass.class, this::inspectDiagnostics));
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
   }
 
-  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
-    // TODO(b/175542052): Should also not have any diagnostics with -dontwarn Main.
-    if (getDontWarnConfiguration().isDontWarnMissingClass()) {
-      diagnostics.assertNoMessages();
-      return;
-    }
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
 
-    diagnostics
-        .assertOnlyErrors()
-        .assertErrorsCount(1)
-        .assertAllErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
-
-    MissingClassesDiagnostic diagnostic = (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
-    assertEquals(
-        !getDontWarnConfiguration().isDontWarnMissingClass(),
-        diagnostic.getMissingClasses().stream()
-            .map(TypeReference::getTypeName)
-            .anyMatch(MissingClass.class.getTypeName()::equals));
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
   }
 
   static class Main {
@@ -57,6 +53,4 @@
       new MissingClass();
     }
   }
-
-  static class MissingClass {}
 }
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromOuterClassAttributeTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromOuterClassAttributeTest.java
new file mode 100644
index 0000000..d707f74
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromOuterClassAttributeTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.missingclasses.MissingClassReferencedFromOuterClassAttributeTest.MissingClass.Main;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class MissingClassReferencedFromOuterClassAttributeTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  @Parameters(name = "{1}, report: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  private final boolean reportMissingClassesInInnerClassAttributes;
+
+  public MissingClassReferencedFromOuterClassAttributeTest(
+      boolean reportMissingClassesInInnerClassAttributes, TestParameters parameters) {
+    super(parameters);
+    this.reportMissingClassesInInnerClassAttributes = reportMissingClassesInInnerClassAttributes;
+  }
+
+  @Test
+  public void testNoRules() throws Exception {
+    assertFailsCompilationIf(
+        reportMissingClassesInInnerClassAttributes,
+        () ->
+            compileWithExpectedDiagnostics(
+                Main.class,
+                reportMissingClassesInInnerClassAttributes
+                    ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+                    : TestDiagnosticMessages::assertNoMessages,
+                this::configure));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(this::configure));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(this::configure));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        reportMissingClassesInInnerClassAttributes
+            ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+            : TestDiagnosticMessages::assertNoMessages,
+        addIgnoreWarnings(reportMissingClassesInInnerClassAttributes).andThen(this::configure));
+  }
+
+  void configure(R8FullTestBuilder builder) {
+    builder
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addOptionsModification(
+            options -> {
+              // We do not report missing classes from inner class attributes by default.
+              assertFalse(options.reportMissingClassesInInnerClassAttributes);
+              options.reportMissingClassesInInnerClassAttributes =
+                  reportMissingClassesInInnerClassAttributes;
+            })
+        .applyIf(
+            reportMissingClassesInInnerClassAttributes,
+            // We need to ignore the inner class attribute for the test class.
+            addDontWarn(MissingClassReferencedFromOuterClassAttributeTest.class),
+            // The -dontwarn Main and -dontwarn MissingClass tests will have unused -dontwarn rules.
+            R8TestBuilder::allowUnusedDontWarnPatterns);
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingClass.class);
+  }
+
+  static class MissingClass {
+
+    static class Main {
+
+      public static void main(String[] args) {}
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticGetToExistingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticGetToExistingFieldTest.java
new file mode 100644
index 0000000..e76c4c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticGetToExistingFieldTest.java
@@ -0,0 +1,64 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class resolves to a definition, then the field
+ * definition is to be blamed.
+ */
+public class MissingClassReferencedFromStaticGetToExistingFieldTest extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(
+          Reference.classFromClass(Main.class),
+          "FIELD",
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromStaticGetToExistingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static MissingClass FIELD;
+
+    public static void main(String[] args) {
+      MissingClass ignore = FIELD;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticGetToMissingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticGetToMissingFieldTest.java
new file mode 100644
index 0000000..36f8a29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticGetToMissingFieldTest.java
@@ -0,0 +1,60 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class does not resolve, then the enclosing method
+ * is to be blamed.
+ */
+public class MissingClassReferencedFromStaticGetToMissingFieldTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromStaticGetToMissingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int ignore = MissingClass.FIELD;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticPutToExistingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticPutToExistingFieldTest.java
new file mode 100644
index 0000000..542c928
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticPutToExistingFieldTest.java
@@ -0,0 +1,64 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class resolves to a definition, then the field
+ * definition is to be blamed.
+ */
+public class MissingClassReferencedFromStaticPutToExistingFieldTest extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(
+          Reference.classFromClass(Main.class),
+          "FIELD",
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromStaticPutToExistingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static MissingClass FIELD;
+
+    public static void main(String[] args) {
+      FIELD = null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticPutToMissingFieldTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticPutToMissingFieldTest.java
new file mode 100644
index 0000000..542d509
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromStaticPutToMissingFieldTest.java
@@ -0,0 +1,60 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+/**
+ * If a field reference that refers to a missing class does not resolve, then the enclosing method
+ * is to be blamed.
+ */
+public class MissingClassReferencedFromStaticPutToMissingFieldTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromStaticPutToMissingFieldTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      MissingClass.FIELD = 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromSuperClassTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromSuperClassTest.java
new file mode 100644
index 0000000..7aeb068
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromSuperClassTest.java
@@ -0,0 +1,62 @@
+// 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.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+public class MissingClassReferencedFromSuperClassTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromSuperClassTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  // The tests explicitly disable desugaring to prevent desugaring warnings.
+  // TODO(b/179341237): Consider if desugaring warnings should be D8 only, since in a way they are
+  //  all duplicates of the MissingClassesDiagnostic.
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        TestCompilerBuilder::disableDesugaring);
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(TestCompilerBuilder::disableDesugaring));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(TestCompilerBuilder::disableDesugaring));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings().andThen(TestCompilerBuilder::disableDesugaring));
+  }
+
+  static class Main extends MissingClass {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromThrowsClauseTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromThrowsClauseTest.java
new file mode 100644
index 0000000..1708330
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromThrowsClauseTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+public class MissingClassReferencedFromThrowsClauseTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromThrowsClauseTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        TestShrinkerBuilder::addKeepAttributeExceptions);
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(TestShrinkerBuilder::addKeepAttributeExceptions));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(TestShrinkerBuilder::addKeepAttributeExceptions));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings().andThen(TestShrinkerBuilder::addKeepAttributeExceptions));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) throws MissingClass {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 7c72273..dafb7b4 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -4,13 +4,27 @@
 
 package com.android.tools.r8.missingclasses;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.ClassReference;
+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.MissingClassesDiagnostic;
+import com.android.tools.r8.utils.FieldReferenceUtils;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableSet;
 import java.util.List;
+import java.util.function.Function;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -18,53 +32,164 @@
 @RunWith(Parameterized.class)
 public abstract class MissingClassesTestBase extends TestBase {
 
-  enum DontWarnConfiguration {
-    DONT_WARN_MAIN_CLASS,
-    DONT_WARN_MISSING_CLASS,
-    NONE;
+  static class MissingClass extends RuntimeException {
 
-    public boolean isDontWarnMainClass() {
-      return this == DONT_WARN_MAIN_CLASS;
-    }
+    static int FIELD;
 
-    public boolean isDontWarnMissingClass() {
-      return this == DONT_WARN_MISSING_CLASS;
-    }
+    int field;
   }
 
-  private final DontWarnConfiguration dontWarnConfiguration;
+  interface MissingInterface {}
+
   private final TestParameters parameters;
 
-  @Parameters(name = "{1}, {0}")
+  @Parameters(name = "{0}")
   public static List<Object[]> data() {
-    return buildParameters(
-        DontWarnConfiguration.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public MissingClassesTestBase(
-      DontWarnConfiguration dontWarnConfiguration, TestParameters parameters) {
-    this.dontWarnConfiguration = dontWarnConfiguration;
+  public MissingClassesTestBase(TestParameters parameters) {
     this.parameters = parameters;
   }
 
-  public R8TestCompileResult compileWithExpectedDiagnostics(
-      Class<?> mainClass, Class<?> missingClass, DiagnosticsConsumer diagnosticsConsumer)
+  public ThrowableConsumer<R8FullTestBuilder> addDontWarn(Class<?> clazz) {
+    return builder -> builder.addDontWarn(clazz);
+  }
+
+  public ThrowableConsumer<R8FullTestBuilder> addIgnoreWarnings() {
+    return addIgnoreWarnings(true);
+  }
+
+  public ThrowableConsumer<R8FullTestBuilder> addIgnoreWarnings(
+      boolean allowDiagnosticWarningMessages) {
+    return builder ->
+        builder.addIgnoreWarnings().allowDiagnosticWarningMessages(allowDiagnosticWarningMessages);
+  }
+
+  public void compileWithExpectedDiagnostics(
+      Class<?> mainClass, DiagnosticsConsumer diagnosticsConsumer)
       throws CompilationFailedException {
-    return testForR8(parameters.getBackend())
-        .addProgramClasses(mainClass)
-        .addKeepMainRule(mainClass)
-        .applyIf(
-            dontWarnConfiguration.isDontWarnMainClass(),
-            testBuilder -> testBuilder.addDontWarn(mainClass))
-        .applyIf(
-            dontWarnConfiguration.isDontWarnMissingClass(),
-            testBuilder -> testBuilder.addDontWarn(missingClass))
+    compileWithExpectedDiagnostics(mainClass, diagnosticsConsumer, null);
+  }
+
+  public void compileWithExpectedDiagnostics(
+      Class<?> mainClass,
+      DiagnosticsConsumer diagnosticsConsumer,
+      ThrowableConsumer<R8FullTestBuilder> configuration)
+      throws CompilationFailedException {
+    internalCompileWithExpectedDiagnostics(
+        diagnosticsConsumer,
+        builder ->
+            builder.addProgramClasses(mainClass).addKeepMainRule(mainClass).apply(configuration));
+  }
+
+  public void compileWithExpectedDiagnostics(
+      ThrowableConsumer<R8FullTestBuilder> configuration, DiagnosticsConsumer diagnosticsConsumer)
+      throws CompilationFailedException {
+    internalCompileWithExpectedDiagnostics(diagnosticsConsumer, configuration);
+  }
+
+  private void internalCompileWithExpectedDiagnostics(
+      DiagnosticsConsumer diagnosticsConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
+      throws CompilationFailedException {
+    testForR8(parameters.getBackend())
+        .apply(configuration)
         .addOptionsModification(TestingOptions::enableExperimentalMissingClassesReporting)
         .setMinApi(parameters.getApiLevel())
         .compileWithExpectedDiagnostics(diagnosticsConsumer);
   }
 
-  public DontWarnConfiguration getDontWarnConfiguration() {
-    return dontWarnConfiguration;
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingClass.class);
+  }
+
+  void inspectDiagnosticsWithIgnoreWarnings(
+      TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
+    inspectDiagnosticsWithIgnoreWarnings(
+        diagnostics,
+        getExpectedDiagnosticMessageWithIgnoreWarnings(
+            referencedFrom, ClassReference::getTypeName));
+  }
+
+  void inspectDiagnosticsWithIgnoreWarnings(
+      TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
+    inspectDiagnosticsWithIgnoreWarnings(
+        diagnostics,
+        getExpectedDiagnosticMessageWithIgnoreWarnings(
+            referencedFrom, FieldReferenceUtils::toSourceString));
+  }
+
+  void inspectDiagnosticsWithIgnoreWarnings(
+      TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
+    inspectDiagnosticsWithIgnoreWarnings(
+        diagnostics,
+        getExpectedDiagnosticMessageWithIgnoreWarnings(
+            referencedFrom, MethodReferenceUtils::toSourceString));
+  }
+
+  void inspectDiagnosticsWithIgnoreWarnings(
+      TestDiagnosticMessages diagnostics, String expectedDiagnosticMessage) {
+    MissingClassesDiagnostic diagnostic =
+        diagnostics
+            .assertOnlyWarnings()
+            .assertWarningsCount(1)
+            .assertAllWarningsMatch(diagnosticType(MissingClassesDiagnostic.class))
+            .getWarning(0);
+    assertEquals(ImmutableSet.of(getMissingClassReference()), diagnostic.getMissingClasses());
+    assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
+  }
+
+  private <T> String getExpectedDiagnosticMessageWithIgnoreWarnings(
+      T referencedFrom, Function<T, String> toSourceStringFunction) {
+    return "Missing class "
+        + getMissingClassReference().getTypeName()
+        + " (referenced from: "
+        + toSourceStringFunction.apply(referencedFrom)
+        + ")";
+  }
+
+  void inspectDiagnosticsWithNoRules(
+      TestDiagnosticMessages diagnostics, ClassReference referencedFrom) {
+    inspectDiagnosticsWithNoRules(
+        diagnostics,
+        getExpectedDiagnosticMessageWithNoRules(referencedFrom, ClassReference::getTypeName));
+  }
+
+  void inspectDiagnosticsWithNoRules(
+      TestDiagnosticMessages diagnostics, FieldReference referencedFrom) {
+    inspectDiagnosticsWithNoRules(
+        diagnostics,
+        getExpectedDiagnosticMessageWithNoRules(
+            referencedFrom, FieldReferenceUtils::toSourceString));
+  }
+
+  void inspectDiagnosticsWithNoRules(
+      TestDiagnosticMessages diagnostics, MethodReference referencedFrom) {
+    inspectDiagnosticsWithNoRules(
+        diagnostics,
+        getExpectedDiagnosticMessageWithNoRules(
+            referencedFrom, MethodReferenceUtils::toSourceString));
+  }
+
+  void inspectDiagnosticsWithNoRules(
+      TestDiagnosticMessages diagnostics, String expectedDiagnosticMessage) {
+    MissingClassesDiagnostic diagnostic =
+        diagnostics
+            .assertOnlyErrors()
+            .assertErrorsCount(1)
+            .assertAllErrorsMatch(diagnosticType(MissingClassesDiagnostic.class))
+            .getError(0);
+    assertEquals(ImmutableSet.of(getMissingClassReference()), diagnostic.getMissingClasses());
+    assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
+  }
+
+  private <T> String getExpectedDiagnosticMessageWithNoRules(
+      T referencedFrom, Function<T, String> toSourceStringFunction) {
+    return "Compilation can't be completed because the following class is missing: "
+        + getMissingClassReference().getTypeName()
+        + " (referenced from: "
+        + toSourceStringFunction.apply(referencedFrom)
+        + ")"
+        + ".";
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
index ddc2da6..2c3f2bb 100644
--- a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
@@ -7,10 +7,8 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -21,11 +19,10 @@
   protected final boolean minification;
 
   AbstractR8KotlinNamingTestBase(
-      KotlinTargetVersion kotlinTargetVersion,
-      KotlinCompiler kotlinc,
+      KotlinTestParameters kotlinParameters,
       boolean allowAccessModification,
       boolean minification) {
-    super(kotlinTargetVersion, kotlinc, allowAccessModification);
+    super(kotlinParameters, allowAccessModification);
     this.minification = minification;
   }
 
@@ -41,56 +38,27 @@
     return classSubject;
   }
 
-  protected FieldSubject checkFieldIsRenamed(
-      ClassSubject classSubject, String fieldType, String fieldName) {
-    FieldSubject fieldSubject = checkFieldIsKept(classSubject, fieldType, fieldName);
-    assertThat(fieldSubject, isPresentAndRenamed());
-    return fieldSubject;
-  }
-
   protected FieldSubject checkFieldIsRenamed(ClassSubject classSubject, String fieldName) {
     FieldSubject fieldSubject = checkFieldIsKept(classSubject, fieldName);
     assertThat(fieldSubject, isPresentAndRenamed());
     return fieldSubject;
   }
 
-  protected FieldSubject checkFieldIsNotRenamed(
-      ClassSubject classSubject, String fieldType, String fieldName) {
-    FieldSubject fieldSubject = checkFieldIsKept(classSubject, fieldType, fieldName);
-    assertThat(fieldSubject, isPresentAndNotRenamed());
-    return fieldSubject;
-  }
-
   protected FieldSubject checkFieldIsNotRenamed(ClassSubject classSubject, String fieldName) {
     FieldSubject fieldSubject = checkFieldIsKept(classSubject, fieldName);
     assertThat(fieldSubject, isPresentAndNotRenamed());
     return fieldSubject;
   }
 
-  protected MethodSubject checkMethodIsRenamed(
-      ClassSubject classSubject, MethodSignature methodSignature) {
-    MethodSubject methodSubject = checkMethodIsKept(classSubject, methodSignature);
-    assertThat(methodSubject, isPresentAndRenamed());
-    return methodSubject;
-  }
-
   protected MethodSubject checkMethodIsRenamed(ClassSubject classSubject, String methodName) {
     MethodSubject methodSubject = checkMethodIsKept(classSubject, methodName);
     assertThat(methodSubject, isPresentAndRenamed());
     return methodSubject;
   }
 
-  protected MethodSubject checkMethodIsNotRenamed(
-      ClassSubject classSubject, MethodSignature methodSignature) {
-    MethodSubject methodSubject = checkMethodIsKept(classSubject, methodSignature);
-    assertThat(methodSubject, isPresentAndNotRenamed());
-    return methodSubject;
-  }
-
   protected MethodSubject checkMethodIsNotRenamed(ClassSubject classSubject, String methodName) {
     MethodSubject methodSubject = checkMethodIsKept(classSubject, methodName);
     assertThat(methodSubject, isPresentAndNotRenamed());
     return methodSubject;
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index b96ff51..f911ce6 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -3,17 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 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.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -31,21 +29,17 @@
   private final TestParameters parameters;
   private final boolean minify;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}, minify: {3}")
+  @Parameterized.Parameters(name = "{0}, {1}, minify: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public EnumMinificationKotlinTest(
-      TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
-      boolean minify) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters, boolean minify) {
+    super(kotlinParameters);
     this.parameters = parameters;
     this.minify = minify;
   }
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 7a1b6e4..5d9e78b 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -3,17 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestCompileResult;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.kotlin.TestKotlinClass;
@@ -39,21 +37,19 @@
 public class KotlinIntrinsicsIdentifierTest extends AbstractR8KotlinNamingTestBase {
   private static final String FOLDER = "intrinsics_identifiers";
 
-  @Parameters(name = "target: {0}, kotlinc: {1}, allowAccessModification: {2}, minification: {3}")
+  @Parameters(name = "{0}, allowAccessModification: {1}, minification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values(),
         BooleanUtils.values());
   }
 
   public KotlinIntrinsicsIdentifierTest(
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
+      KotlinTestParameters kotlinParameters,
       boolean allowAccessModification,
       boolean minification) {
-    super(targetVersion, kotlinc, allowAccessModification, minification);
+    super(kotlinParameters, allowAccessModification, minification);
   }
 
   private static final KotlinCompileMemoizer compiledJars =
diff --git a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
index 69f0148..665e410 100644
--- a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.naming.b139991218;
 
-import static junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.assertFalse;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
@@ -14,8 +14,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.nio.file.Paths;
@@ -63,21 +61,25 @@
             options -> {
               options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
               options.enableClassInlining = false;
+
+              // TODO(b/179019716): Add support for merging in presence of annotations.
+              options.horizontalClassMergerOptions()
+                      .skipNoClassesOrMembersWithAnnotationsPolicyForTesting =
+                  true;
             })
         .addDontWarnJetBrainsAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(
+                    "com.android.tools.r8.naming.b139991218.Lambda1",
+                    "com.android.tools.r8.naming.b139991218.Lambda2"))
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(StringUtils.lines("11", "12"))
+        .assertSuccessWithOutputLines("11", "12")
         .inspect(
-            inspector -> {
-              // Ensure that we have created a lambda group and that the lambda classes are now
-              // gone.
-              boolean foundLambdaGroup = false;
-              for (FoundClassSubject allClass : inspector.allClasses()) {
-                foundLambdaGroup |= allClass.getOriginalName().contains("LambdaGroup");
-                assertFalse(allClass.getOriginalName().contains("b139991218.Lambda"));
-              }
-              assertTrue(foundLambdaGroup);
-            });
+            inspector ->
+                assertThat(
+                    inspector.clazz("com.android.tools.r8.naming.b139991218.Lambda1"),
+                    isPresent()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
index 397f703..942ae66 100644
--- a/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
+++ b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
@@ -48,7 +48,7 @@
 
     testForD8(Backend.DEX)
         .addProgramClassFileData(bytes)
-        .setDisableDesugaring(true)
+        .disableDesugaring()
         .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
         .addOptionsModification(options -> options.testing.disableStackMapVerification = true)
         .compile();
diff --git a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
index 927decb..d01d917 100644
--- a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
@@ -46,10 +46,6 @@
                 .build()));
   }
 
-  private boolean isDesugaring() {
-    return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
-  }
-
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
diff --git a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
index 196ca52..cd3743f 100644
--- a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
+++ b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
@@ -17,7 +17,7 @@
   public void test() throws CompilationFailedException, IOException {
     testForD8()
         .addProgramClassFileData(Regress77842465Dump.dump())
-        .noDesugaring()
+        .disableDesugaring()
         .setMinApi(AndroidApiLevel.M)
         .compile()
         .runDex2Oat(new DexRuntime(ToolHelper.getDexVm()))
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 94e1e1a..2479dce 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
@@ -16,11 +15,10 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -44,18 +42,17 @@
 
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
     // TODO(b/141817471): Extend with compilation modes.
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public KotlinInlineFunctionInSameFileRetraceTests(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 3827c26..945b732 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
@@ -17,11 +16,10 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -45,18 +43,17 @@
   private static final String FILENAME_INLINE_STATIC = "InlineFunction.kt";
   private static final String FILENAME_INLINE_INSTANCE = "InlineFunction.kt";
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
     // TODO(b/141817471): Extend with compilation modes.
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers());
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
   public KotlinInlineFunctionRetraceTest(
-      TestParameters parameters, KotlinTargetVersion targetVersion, KotlinCompiler kotlinc) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
index 64ea164..2cc7c96 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
@@ -15,8 +14,8 @@
 
 import com.android.tools.r8.AssertionsConfiguration;
 import com.android.tools.r8.D8TestBuilder;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
@@ -63,24 +62,21 @@
   private final boolean useJvmAssertions;
   private final KotlinCompileMemoizer compiledForAssertions;
 
-  @Parameterized.Parameters(
-      name = "{0}, target: {1}, kotlinc: {2}, kotlin-stdlib as library: {3}, -Xassertions=jvm: {4}")
+  @Parameterized.Parameters(name = "{0}, {1}, kotlin-stdlib as library: {2}, -Xassertions=jvm: {3}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values(),
         BooleanUtils.values());
   }
 
   public AssertionConfigurationKotlinTest(
       TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
+      KotlinTestParameters kotlinParameters,
       boolean kotlinStdlibAsClasspath,
       boolean useJvmAssertions) {
-    super(targetVersion, kotlinc);
+    super(kotlinParameters);
     this.parameters = parameters;
     this.kotlinStdlibAsLibrary = kotlinStdlibAsClasspath;
     this.useJvmAssertions = useJvmAssertions;
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 5ad87de..7ba46d2 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
 import static com.android.tools.r8.ToolHelper.EXAMPLES_DIR;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.Diagnostic;
@@ -26,7 +27,6 @@
 import java.util.Collection;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -92,12 +92,10 @@
         new DiagnosticsHandler() {
           @Override
           public void error(Diagnostic error) {
-            String expectedErrorMessage =
-                "Compilation can't be completed because the class java.lang.Object is missing.";
-            if (error.getDiagnosticMessage().equals(expectedErrorMessage)) {
-              return;
-            }
-            throw new RuntimeException("Unexpected compilation error");
+            assertEquals(
+                "Compilation can't be completed because the following class is missing: "
+                    + "java.lang.Object.",
+                error.getDiagnosticMessage());
           }
         };
     R8Command.Builder builder = R8Command.builder(handler)
@@ -146,7 +144,7 @@
                     "shaking1",
                     "print-mapping-" + backend.name().toLowerCase() + ".ref")),
             StandardCharsets.UTF_8);
-    Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
+    assertEquals(sorted(refMapping), sorted(actualMapping));
   }
 
   private static String sorted(String str) {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index bc55c55..8eaee28 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.annotations;
 
-import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -13,10 +12,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -62,21 +60,17 @@
   private final TestParameters parameters;
   private final boolean minify;
 
-  @Parameterized.Parameters(name = "{0}, target: {1}, kotlinc: {2}, minify: {3}")
+  @Parameterized.Parameters(name = "{0}, {1}, minify: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        KotlinTargetVersion.values(),
-        getKotlinCompilers(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public ReflectiveAnnotationUseTest(
-      TestParameters parameters,
-      KotlinTargetVersion targetVersion,
-      KotlinCompiler kotlinc,
-      boolean minify) {
-    super(targetVersion, kotlinc);
+      TestParameters parameters, KotlinTestParameters kotlinParameters, boolean minify) {
+    super(kotlinParameters);
     this.parameters = parameters;
     this.minify = minify;
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index 6f9c298..5d5a4b9 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -51,8 +51,8 @@
         .addKeepClassRules(Interface.class)
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedLambdaClassesInspector(
-            inspector -> inspector.assertClassNotMerged(EventPublisher$b.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertClassesNotMerged(EventPublisher$b.class))
         .compile();
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index 1a61008..a14683a 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
+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.WhyAreYouKeepingConsumer;
@@ -75,6 +76,7 @@
     MethodReference fooClInitRef = Reference.classConstructor(fooClassRef);
     MethodReference fooInitRef = Reference.methodFromMethod(fooClass.getDeclaredConstructor());
 
+    FieldReference testFooFieldRef = Reference.fieldFromField(testClass.getDeclaredField("foo"));
     MethodReference testInitRef =
         Reference.methodFromMethod(testClass.getDeclaredConstructor());
 
@@ -96,8 +98,16 @@
     QueryNode mainMethod = inspector.method(mainMethodRef).assertNotRenamed().assertKeptBy(root);
     // TestClass.<init> is kept by TestClass.main.
     QueryNode testInit = inspector.method(testInitRef).assertPresent().assertKeptBy(mainMethod);
-    // The type Foo is kept by TestClass.<init>
-    QueryNode fooClassNode = inspector.clazz(fooClassRef).assertRenamed().assertKeptBy(testInit);
+    // TestClass.foo is kept by TestClass.<init>.
+    QueryNode testFooFieldNode =
+        inspector.field(testFooFieldRef).assertPresent().assertKeptBy(testInit);
+    // The type Foo is not kept by TestClass.<init>, but TestClass.foo.
+    QueryNode fooClassNode =
+        inspector
+            .clazz(fooClassRef)
+            .assertRenamed()
+            .assertKeptBy(testFooFieldNode)
+            .assertNotKeptBy(testInit);
     // Foo.<clinit> is kept by Foo
     QueryNode fooClInit = inspector.method(fooClInitRef).assertPresent().assertKeptBy(fooClassNode);
     // The type Foo is also kept by Foo.<clinit>
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 3f9c6af..6f3a6cc 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -86,6 +86,10 @@
     return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.OUTLINE);
   }
 
+  public static boolean isInitializerTypeArgument(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT);
+  }
+
   public static Matcher<String> containsInternalSyntheticReference() {
     return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
   }
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceStaticFieldIndirectionTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceStaticFieldIndirectionTest.java
new file mode 100644
index 0000000..b0e6069
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceStaticFieldIndirectionTest.java
@@ -0,0 +1,110 @@
+// 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.tracereferences;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+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 TraceReferenceStaticFieldIndirectionTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public TraceReferenceStaticFieldIndirectionTest(TestParameters parameters) {}
+
+  static class MissingReferencesConsumer implements TraceReferencesConsumer {
+
+    private Set<FieldReference> seenFields = new HashSet<>();
+
+    @Override
+    public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {}
+
+    @Override
+    public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {
+      seenFields.add(tracedField.getReference());
+    }
+
+    @Override
+    public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) {}
+  }
+
+  @Test
+  public void traceStaticFields() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(SuperClass.class),
+                ToolHelper.getClassFileForTestClass(SubClass.class))
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(Main.class))
+            .build();
+    DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
+    MissingReferencesConsumer consumer = new MissingReferencesConsumer();
+
+    TraceReferences.run(
+        TraceReferencesCommand.builder(diagnosticsChecker)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addSourceFiles(sourceJar)
+            .addTargetFiles(targetJar)
+            .setConsumer(consumer)
+            .build());
+
+    ImmutableSet<FieldReference> expectedSet =
+        ImmutableSet.of(
+            Reference.field(
+                Reference.classFromClass(SuperClass.class),
+                "field1",
+                Reference.typeFromTypeName("int")),
+            Reference.field(
+                Reference.classFromClass(SubClass.class),
+                "field2",
+                Reference.typeFromTypeName("int")));
+    assertEquals(expectedSet, consumer.seenFields);
+  }
+
+  static class SuperClass {
+
+    static int field1 = 1;
+  }
+
+  static class SubClass extends SuperClass {
+
+    static int field2 = 2;
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(SubClass.field1);
+      System.out.println(SubClass.field2);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceStaticMethodIndirectionTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceStaticMethodIndirectionTest.java
new file mode 100644
index 0000000..3c35e76
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferenceStaticMethodIndirectionTest.java
@@ -0,0 +1,117 @@
+// 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.tracereferences;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+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 TraceReferenceStaticMethodIndirectionTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public TraceReferenceStaticMethodIndirectionTest(TestParameters parameters) {}
+
+  static class MissingReferencesConsumer implements TraceReferencesConsumer {
+
+    private Set<MethodReference> seenMethods = new HashSet<>();
+
+    @Override
+    public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {}
+
+    @Override
+    public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {}
+
+    @Override
+    public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) {
+      seenMethods.add(tracedMethod.getReference());
+    }
+  }
+
+  @Test
+  public void traceStaticMethods() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(SuperClass.class),
+                ToolHelper.getClassFileForTestClass(SubClass.class))
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(Main.class))
+            .build();
+    DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
+    MissingReferencesConsumer consumer = new MissingReferencesConsumer();
+
+    TraceReferences.run(
+        TraceReferencesCommand.builder(diagnosticsChecker)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addSourceFiles(sourceJar)
+            .addTargetFiles(targetJar)
+            .setConsumer(consumer)
+            .build());
+
+    ImmutableSet<MethodReference> expectedSet =
+        ImmutableSet.of(
+            Reference.method(
+                Reference.classFromClass(SuperClass.class),
+                "method1",
+                Collections.emptyList(),
+                null),
+            Reference.method(
+                Reference.classFromClass(SubClass.class),
+                "method2",
+                Collections.emptyList(),
+                null));
+    assertEquals(expectedSet, consumer.seenMethods);
+  }
+
+  static class SuperClass {
+
+    static void method1() {
+      System.out.println("SuperClass::method1");
+    }
+  }
+
+  static class SubClass extends SuperClass {
+
+    static void method2() {
+      System.out.println("SuperClass::method2");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      SuperClass.method1();
+      SubClass.method2();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index cb09a17..491bf9d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.references.ClassReference;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.rules.TemporaryFolder;
 
@@ -38,7 +39,7 @@
   }
 
   @Override
-  public MethodSubject uniqueInstanceInitializer() {
+  public MethodSubject uniqueMethodThatMatches(Predicate<FoundMethodSubject> predicate) {
     return new AbsentMethodSubject();
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
index e9634a5..174e734 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
@@ -9,6 +9,8 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.utils.ThrowingAction;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
 import java.util.function.Consumer;
 
 public class AssertUtils {
@@ -54,7 +56,7 @@
         action.execute();
         fail("Expected action to fail with an exception, but succeeded");
       } catch (Throwable e) {
-        assertEquals(clazz, e.getClass());
+        assertEquals(printStackTraceToString(e), clazz, e.getClass());
         if (consumer != null) {
           consumer.accept(e);
         }
@@ -63,4 +65,12 @@
       action.execute();
     }
   }
+
+  private static String printStackTraceToString(Throwable e) {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    try (PrintStream ps = new PrintStream(baos)) {
+      e.printStackTrace(ps);
+    }
+    return baos.toString();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index cec4e2b..ee96d6b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -96,7 +96,11 @@
 
   public abstract MethodSubject method(String returnType, String name, List<String> parameters);
 
-  public abstract MethodSubject uniqueInstanceInitializer();
+  public final MethodSubject uniqueInstanceInitializer() {
+    return uniqueMethodThatMatches(FoundMethodSubject::isInstanceInitializer);
+  }
+
+  public abstract MethodSubject uniqueMethodThatMatches(Predicate<FoundMethodSubject> predicate);
 
   public abstract MethodSubject uniqueMethodWithName(String name);
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 0bfe560..ed04f35 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -46,6 +46,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.rules.TemporaryFolder;
 
@@ -128,9 +129,9 @@
   }
 
   @Override
-  public MethodSubject uniqueInstanceInitializer() {
+  public MethodSubject uniqueMethodThatMatches(Predicate<FoundMethodSubject> predicate) {
     MethodSubject methodSubject = null;
-    for (FoundMethodSubject candidate : allMethods(FoundMethodSubject::isInstanceInitializer)) {
+    for (FoundMethodSubject candidate : allMethods(predicate)) {
       assert methodSubject == null;
       methodSubject = candidate;
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index e90df17..a5c623c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -8,16 +8,25 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class HorizontallyMergedClassesInspector {
 
@@ -59,8 +68,32 @@
 
   public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) {
     assertEquals(
-        horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from, dexItemFactory)),
-        toDexType(target, dexItemFactory));
+        horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from)), toDexType(target));
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertClassesMerged(Collection<Class<?>> classes) {
+    return assertTypesMerged(classes.stream().map(this::toDexType).collect(Collectors.toList()));
+  }
+
+  public HorizontallyMergedClassesInspector assertClassReferencesMerged(
+      Collection<ClassReference> classReferences) {
+    return assertTypesMerged(
+        classReferences.stream().map(this::toDexType).collect(Collectors.toList()));
+  }
+
+  public HorizontallyMergedClassesInspector assertTypesMerged(Collection<DexType> types) {
+    List<DexType> unmerged = new ArrayList<>();
+    for (DexType type : types) {
+      if (!horizontallyMergedClasses.hasBeenMergedOrIsMergeTarget(type)) {
+        unmerged.add(type);
+      }
+    }
+    assertEquals(
+        "Expected the following classes to be merged: "
+            + StringUtils.join(", ", unmerged, DexType::getTypeName),
+        0,
+        unmerged.size());
     return this;
   }
 
@@ -69,52 +102,91 @@
     return this;
   }
 
-  public HorizontallyMergedClassesInspector assertClassNotMerged(Class<?> clazz) {
-    assertFalse(
-        horizontallyMergedClasses.hasBeenMergedIntoDifferentType(toDexType(clazz, dexItemFactory)));
-    return this;
-  }
-
-  public HorizontallyMergedClassesInspector assertClassesNotMerged(Collection<Class<?>> classes) {
-    for (Class<?> clazz : classes) {
-      assertClassNotMerged(clazz);
-    }
-    return this;
-  }
-
   public HorizontallyMergedClassesInspector assertClassesNotMerged(Class<?>... classes) {
     return assertClassesNotMerged(Arrays.asList(classes));
   }
 
-  public HorizontallyMergedClassesInspector assertClassNotMergedIntoDifferentType(Class<?> clazz) {
-    assertFalse(
-        horizontallyMergedClasses.hasBeenMergedIntoDifferentType(toDexType(clazz, dexItemFactory)));
-    return this;
+  public HorizontallyMergedClassesInspector assertClassesNotMerged(Collection<Class<?>> classes) {
+    return assertTypesNotMerged(classes.stream().map(this::toDexType).collect(Collectors.toList()));
   }
 
-  public HorizontallyMergedClassesInspector assertMerged(Class<?> clazz) {
-    assertTrue(
-        horizontallyMergedClasses.hasBeenMergedOrIsMergeTarget(toDexType(clazz, dexItemFactory)));
-    return this;
+  public HorizontallyMergedClassesInspector assertClassReferencesNotMerged(
+      ClassReference... classReferences) {
+    return assertClassReferencesNotMerged(Arrays.asList(classReferences));
   }
 
-  public HorizontallyMergedClassesInspector assertMerged(Class<?>... classes) {
-    for (Class<?> clazz : classes) {
-      assertMerged(clazz);
+  public HorizontallyMergedClassesInspector assertClassReferencesNotMerged(
+      Collection<ClassReference> classReferences) {
+    return assertTypesNotMerged(
+        classReferences.stream().map(this::toDexType).collect(Collectors.toList()));
+  }
+
+  public HorizontallyMergedClassesInspector assertTypesNotMerged(DexType... types) {
+    return assertTypesNotMerged(Arrays.asList(types));
+  }
+
+  public HorizontallyMergedClassesInspector assertTypesNotMerged(Collection<DexType> types) {
+    for (DexType type : types) {
+      assertTrue(type.isClassType());
+      assertFalse(horizontallyMergedClasses.hasBeenMergedOrIsMergeTarget(type));
     }
     return this;
   }
 
-  public HorizontallyMergedClassesInspector assertMergedIntoDifferentType(Class<?> clazz) {
-    assertTrue(
-        horizontallyMergedClasses.hasBeenMergedIntoDifferentType(toDexType(clazz, dexItemFactory)));
+  public HorizontallyMergedClassesInspector assertIsCompleteMergeGroup(String... typeNames) {
+    return assertIsCompleteMergeGroup(
+        Stream.of(typeNames).map(Reference::classFromTypeName).collect(Collectors.toList()));
+  }
+
+  public HorizontallyMergedClassesInspector assertIsCompleteMergeGroup(
+      Collection<ClassReference> classReferences) {
+    assertFalse(classReferences.isEmpty());
+    List<DexType> types =
+        classReferences.stream().map(this::toDexType).collect(Collectors.toList());
+    DexType uniqueTarget = null;
+    for (DexType type : types) {
+      if (horizontallyMergedClasses.isMergeTarget(type)) {
+        if (uniqueTarget == null) {
+          uniqueTarget = type;
+        } else {
+          fail(
+              "Expected a single merge target, but found "
+                  + type.getTypeName()
+                  + " and "
+                  + uniqueTarget.getTypeName());
+        }
+      }
+    }
+    if (uniqueTarget == null) {
+      for (DexType type : types) {
+        if (horizontallyMergedClasses.hasBeenMergedIntoDifferentType(type)) {
+          fail(
+              "Expected merge target "
+                  + horizontallyMergedClasses.getMergeTargetOrDefault(type).getTypeName()
+                  + " to be in merge group");
+        }
+      }
+      fail("Expected to find a merge target, but none found");
+    }
+    Set<DexType> sources = horizontallyMergedClasses.getSourcesFor(uniqueTarget);
+    assertEquals(
+        "Expected to find "
+            + (classReferences.size() - 1)
+            + " source(s) for merge target "
+            + uniqueTarget.getTypeName()
+            + ", but only found: "
+            + StringUtils.join(", ", sources, DexType::getTypeName),
+        classReferences.size() - 1,
+        sources.size());
+    assertTrue(types.containsAll(sources));
     return this;
   }
 
-  public HorizontallyMergedClassesInspector assertMergedIntoDifferentType(Class<?>... classes) {
-    for (Class<?> clazz : classes) {
-      assertMergedIntoDifferentType(clazz);
-    }
-    return this;
+  private DexType toDexType(Class<?> clazz) {
+    return TestBase.toDexType(clazz, dexItemFactory);
+  }
+
+  private DexType toDexType(ClassReference classReference) {
+    return TestBase.toDexType(classReference, dexItemFactory);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java
deleted file mode 100644
index 619b415..0000000
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedLambdaClassesInspector.java
+++ /dev/null
@@ -1,53 +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.utils.codeinspector;
-
-import static com.android.tools.r8.TestBase.toDexType;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
-import java.util.Set;
-import java.util.function.BiConsumer;
-
-public class HorizontallyMergedLambdaClassesInspector {
-
-  private final DexItemFactory dexItemFactory;
-  private final HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
-
-  public HorizontallyMergedLambdaClassesInspector(
-      DexItemFactory dexItemFactory,
-      HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses) {
-    this.dexItemFactory = dexItemFactory;
-    this.horizontallyMergedLambdaClasses = horizontallyMergedLambdaClasses;
-  }
-
-  public HorizontallyMergedLambdaClassesInspector assertMerged(Class<?> clazz) {
-    assertTrue(
-        horizontallyMergedLambdaClasses.hasBeenMergedIntoDifferentType(
-            toDexType(clazz, dexItemFactory)));
-    return this;
-  }
-
-  public HorizontallyMergedLambdaClassesInspector assertMerged(Class<?>... classes) {
-    for (Class<?> clazz : classes) {
-      assertMerged(clazz);
-    }
-    return this;
-  }
-
-  public HorizontallyMergedLambdaClassesInspector assertClassNotMerged(Class<?> clazz) {
-    assertFalse(
-        horizontallyMergedLambdaClasses.hasBeenMergedIntoDifferentType(
-            toDexType(clazz, dexItemFactory)));
-    return this;
-  }
-
-  public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
-    horizontallyMergedLambdaClasses.forEachMergeGroup(consumer);
-  }
-}