Merge commit '072f610d7f11a5d9f6614e5d1c9e847383db5efe' into dev-release
diff --git a/.gitignore b/.gitignore
index aa4c9ae..b382c4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,6 @@
 !third_party/gmscore/*.sha1
 !third_party/internal/*.sha1
 !third_party/nest/*.sha1
-!third_party/photos/*.sha1
 !third_party/proguard/*.sha1
 !third_party/youtube/*sha1
 #*#
@@ -125,7 +124,6 @@
 third_party/openjdk/openjdk-rt-1.8.tar.gz
 third_party/opensource_apps
 third_party/opensource_apps.tar.gz
-third_party/photos/*
 third_party/proguard/*
 third_party/proguardsettings.tar.gz
 third_party/proguardsettings/
diff --git a/build.gradle b/build.gradle
index 2e2132d..730d291 100644
--- a/build.gradle
+++ b/build.gradle
@@ -425,7 +425,6 @@
         "gmscore/v7",
         "gmscore/v8",
         "nest/nest_20180926_7c6cfb",
-        "photos/2017-06-06",
         "proguard/proguard_internal_159423826",
         "proguardsettings",
         "proto",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 99ff6a7..3651899 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -165,8 +165,9 @@
       throws IOException {
     PrefixRewritingMapper rewritePrefix =
         options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
-    LazyLoadedDexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
-    AppInfo appInfo = AppInfo.createInitialAppInfo(app);
+    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
+    LazyLoadedDexApplication app = applicationReader.read(executor);
+    AppInfo appInfo = AppInfo.createInitialAppInfo(app, applicationReader.readMainDexClasses(app));
     return AppView.createForD8(appInfo, rewritePrefix);
   }
 
@@ -280,13 +281,15 @@
           DexApplication app =
               rewriteNonDexInputs(
                   appView, inputApp, options, executor, timing, appView.appInfo().app());
-          appView.setAppInfo(new AppInfo(app, appView.appInfo().getSyntheticItems().commit(app)));
+          appView.setAppInfo(
+              new AppInfo(
+                  app,
+                  appView.appInfo().getMainDexClasses(),
+                  appView.appInfo().getSyntheticItems().commit(app)));
           namingLens = NamingLens.getIdentityLens();
         }
         new ApplicationWriter(
-                appView.appInfo().app(),
                 appView,
-                options,
                 marker == null ? null : ImmutableList.copyOf(markers),
                 GraphLens.getIdentityLens(),
                 InitClassLens.getDefault(),
@@ -307,7 +310,7 @@
   }
 
   private static DexApplication rewriteNonDexInputs(
-      AppView<?> appView,
+      AppView<AppInfo> appView,
       AndroidApp inputApp,
       InternalOptions options,
       ExecutorService executor,
@@ -333,6 +336,11 @@
       }
     }
     DexApplication cfApp = app.builder().replaceProgramClasses(nonDexProgramClasses).build();
+    appView.setAppInfo(
+        new AppInfo(
+            cfApp,
+            appView.appInfo().getMainDexClasses(),
+            appView.appInfo().getSyntheticItems().commit(cfApp)));
     ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
     NamingLens prefixRewritingNamingLens =
         PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
@@ -340,9 +348,7 @@
         .run(appView.appInfo().classes(), executor);
     new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForD8(executor);
     new ApplicationWriter(
-            cfApp,
             appView,
-            options,
             null,
             GraphLens.getIdentityLens(),
             InitClassLens.getDefault(),
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 49ab77b..4d1c0dc 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -85,13 +85,17 @@
     try {
       try {
         Timing timing = new Timing("DexFileMerger");
+        ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
         DexApplication app =
-            new ApplicationReader(inputApp, options, timing)
-                .read(
-                    null,
-                    executor,
-                    new DexFileMergerHelper(inputOrdering)::keepFirstProgramClassConflictResolver);
-        AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(app));
+            applicationReader.read(
+                null,
+                executor,
+                new DexFileMergerHelper(inputOrdering)::keepFirstProgramClassConflictResolver);
+
+        AppView<AppInfo> appView =
+            AppView.createForD8(
+                AppInfo.createInitialAppInfo(app, applicationReader.readMainDexClasses(app)));
+
         D8.optimize(appView, options, timing, executor);
 
         List<Marker> markers = appView.dexItemFactory().extractMarkers();
@@ -99,9 +103,7 @@
         assert !options.hasMethodsFilter();
         ApplicationWriter writer =
             new ApplicationWriter(
-                appView.appInfo().app(),
-                null,
-                options,
+                appView,
                 markers,
                 GraphLens.getIdentityLens(),
                 InitClassLens.getDefault(),
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 9935d84..8b5d011 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -19,6 +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.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FeatureClassMapping;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
@@ -71,8 +72,10 @@
 
     try {
       Timing timing = new Timing("DexSplitter");
-      DexApplication app =
-          new ApplicationReader(command.getInputApp(), options, timing).read(null, executor);
+      ApplicationReader applicationReader =
+          new ApplicationReader(command.getInputApp(), options, timing);
+      DexApplication app = applicationReader.read(executor);
+      MainDexClasses mainDexClasses = applicationReader.readMainDexClasses(app);
 
       List<Marker> markers = app.dexItemFactory.extractMarkers();
 
@@ -83,13 +86,20 @@
       Map<String, LazyLoadedDexApplication.Builder> applications =
           getDistribution(app, featureClassMapping, mapper);
       for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) {
+        String feature = entry.getKey();
         DexApplication featureApp = entry.getValue().build();
         assert !options.hasMethodsFilter();
 
-        // Run d8 optimize to ensure jumbo strings are handled.
-        AppInfo appInfo = AppInfo.createInitialAppInfo(featureApp);
+        // If this is the base, we add the main dex list.
+        AppInfo appInfo =
+            feature.equals(featureClassMapping.getBaseName())
+                ? AppInfo.createInitialAppInfo(featureApp, mainDexClasses)
+                : AppInfo.createInitialAppInfo(featureApp);
         AppView<AppInfo> appView = AppView.createForD8(appInfo);
+
+        // Run d8 optimize to ensure jumbo strings are handled.
         D8.optimize(appView, options, timing, executor);
+
         // We create a specific consumer for each split.
         Path outputDir = Paths.get(output).resolve(entry.getKey());
         if (!Files.exists(outputDir)) {
@@ -99,9 +109,7 @@
 
         try {
           new ApplicationWriter(
-                  appView.appInfo().app(),
                   appView,
-                  options,
                   markers,
                   GraphLens.getIdentityLens(),
                   InitClassLens.getDefault(),
@@ -134,10 +142,6 @@
       LazyLoadedDexApplication.Builder featureApplication = applications.get(feature);
       if (featureApplication == null) {
         featureApplication = DexApplication.builder(app.options, app.timing);
-        // If this is the base, we add the main dex list.
-        if (feature.equals(featureClassMapping.getBaseName())) {
-          featureApplication.addToMainDexList(app.mainDexList);
-        }
         applications.put(feature, featureApplication);
       }
       featureApplication.addProgramClass(clazz);
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index c8d6295..9492e12 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -103,8 +103,7 @@
     InternalOptions options = new InternalOptions();
     options.skipReadingDexCode = true;
     options.minApiLevel = AndroidApiLevel.P.getLevel();
-    DexApplication dexApp =
-        new ApplicationReader(app, options, new Timing("ExtractMarker")).read();
+    DexApplication dexApp = new ApplicationReader(app, options, new Timing("ExtractMarker")).read();
     return dexApp.dexItemFactory.extractMarkers();
   }
 
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 8144903..74da57f 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
@@ -284,7 +285,7 @@
     // Build a plain text file with the desugared APIs.
     List<String> desugaredApisSignatures = new ArrayList<>();
 
-    DexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
+    LazyLoadedDexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
     supportedMethods.supportedMethods.forEach(
         (clazz, methods) -> {
           String classBinaryName =
@@ -310,8 +311,7 @@
         lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
 
     // Write a header jar with the desugared APIs.
-    AppInfo appInfo = AppInfo.createInitialAppInfo(builder.build());
-    AppView<?> appView = AppView.createForD8(appInfo);
+    AppView<?> appView = AppView.createForD8(AppInfo.createInitialAppInfo(builder.build()));
     CfApplicationWriter writer =
         new CfApplicationWriter(
             appView,
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 243b37a..04479b1 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -16,8 +16,8 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
-import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.MainDexListBuilder;
+import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
@@ -70,10 +70,10 @@
           EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
       Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
       // LiveTypes is the result.
-      MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, appView).run();
+      MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
 
       List<String> result =
-          mainDexClasses.getClasses().stream()
+          mainDexTracingResult.getClasses().stream()
               .map(c -> c.toSourceString().replace('.', '/') + ".class")
               .sorted()
               .collect(Collectors.toList());
@@ -88,7 +88,7 @@
           () -> {
             ArrayList<DexProgramClass> classes = new ArrayList<>();
             // TODO(b/131668850): This is not a deterministic order!
-            mainDexClasses
+            mainDexTracingResult
                 .getClasses()
                 .forEach(
                     type -> {
diff --git a/src/main/java/com/android/tools/r8/JarSizeCompare.java b/src/main/java/com/android/tools/r8/JarSizeCompare.java
index 83261a3..15e29fa 100644
--- a/src/main/java/com/android/tools/r8/JarSizeCompare.java
+++ b/src/main/java/com/android/tools/r8/JarSizeCompare.java
@@ -53,7 +53,8 @@
           + "    --input <name2> <input2.jar> [<map2.txt>] ...\n"
           + "\n"
           + "JarSizeCompare outputs the class, method, field sizes of the given JAR files.\n"
-          + "For each input, a ProGuard map can be passed that is used to resolve minified names.\n";
+          + "For each input, a ProGuard map can be passed that is used to resolve minified"
+          + " names.\n";
 
   private static final ImmutableMap<String, String> R8_RELOCATIONS =
       ImmutableMap.<String, String>builder()
diff --git a/src/main/java/com/android/tools/r8/PrintClassList.java b/src/main/java/com/android/tools/r8/PrintClassList.java
index 40cdf2e..73cb04d 100644
--- a/src/main/java/com/android/tools/r8/PrintClassList.java
+++ b/src/main/java/com/android/tools/r8/PrintClassList.java
@@ -45,8 +45,7 @@
         new ApplicationReader(builder.build(), new InternalOptions(), new Timing("PrintClassList"))
             .read(
                 proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
-                executorService
-            );
+                executorService);
     ClassNameMapper map = application.getProguardMap();
     for (DexProgramClass clazz : application.classes()) {
       System.out.print(maybeDeobfuscateType(map, clazz.type));
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 47ed5a0..7ffc5ae 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -25,6 +25,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.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -349,7 +350,9 @@
     InternalOptions options = new InternalOptions();
     application =
         new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
-    appInfo = AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(application);
+    appInfo =
+        AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+            application, MainDexClasses.createEmptyMainDexClasses());
   }
 
   private void analyze() {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3a9b0d9..7b94e97 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -91,6 +91,7 @@
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.MainDexListBuilder;
+import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationUtils;
@@ -231,9 +232,7 @@
             .write(options.getClassFileConsumer());
       } else {
         new ApplicationWriter(
-                appView.appInfo().app(),
                 appView,
-                options,
                 // Ensure that the marker for this compilation is the first in the list.
                 ImmutableList.<Marker>builder().add(marker).addAll(markers).build(),
                 graphLens,
@@ -290,13 +289,14 @@
     try {
       AppView<AppInfoWithClassHierarchy> appView;
       {
-        DirectMappedDexApplication application =
-            new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
+        ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
+        DirectMappedDexApplication application = applicationReader.read(executorService).toDirect();
+        MainDexClasses mainDexClasses = applicationReader.readMainDexClasses(application);
 
         // Now that the dex-application is fully loaded, close any internal archive providers.
         inputApp.closeInternalArchiveProviders();
 
-        appView = AppView.createForR8(application);
+        appView = AppView.createForR8(application, mainDexClasses);
         appView.setAppServices(AppServices.builder(appView).build());
       }
 
@@ -451,7 +451,7 @@
       // 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;
-      MainDexClasses mainDexClasses = MainDexClasses.NONE;
+      MainDexTracingResult mainDexTracingResult = MainDexTracingResult.NONE;
       if (!options.mainDexKeepRules.isEmpty()) {
         assert appView.graphLens().isIdentityLens();
         // Find classes which may have code executed before secondary dex files installation.
@@ -464,7 +464,7 @@
             EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo)
                 .traceMainDex(mainDexRootSet, executorService, timing);
         // Calculate the automatic main dex list according to legacy multidex constraints.
-        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, appView).run();
+        mainDexTracingResult = new MainDexListBuilder(mainDexBaseClasses, appView).run();
         appView.appInfo().unsetObsolete();
       }
 
@@ -523,7 +523,7 @@
         if (options.enableStaticClassMerging) {
           timing.begin("HorizontalStaticClassMerger");
           StaticClassMerger staticClassMerger =
-              new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
+              new StaticClassMerger(appViewWithLiveness, options, mainDexTracingResult);
           NestedGraphLens lens = staticClassMerger.run();
           appView.rewriteWithLens(lens);
           timing.end();
@@ -536,7 +536,7 @@
                   appViewWithLiveness,
                   executorService,
                   timing,
-                  mainDexClasses);
+                  mainDexTracingResult);
           VerticalClassMergerGraphLens lens = verticalClassMerger.run();
           if (lens != null) {
             appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
@@ -548,7 +548,7 @@
           timing.begin("HorizontalClassMerger");
           HorizontalClassMerger merger =
               new HorizontalClassMerger(
-                  appViewWithLiveness, mainDexClasses, classMergingEnqueuerExtension);
+                  appViewWithLiveness, mainDexTracingResult, classMergingEnqueuerExtension);
           HorizontalClassMergerGraphLens lens = merger.run();
           if (lens != null) {
             appView.setHorizontallyMergedClasses(lens.getHorizontallyMergedClasses());
@@ -613,7 +613,7 @@
       timing.begin("Create IR");
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
-        IRConverter converter = new IRConverter(appView, timing, printer, mainDexClasses);
+        IRConverter converter = new IRConverter(appView, timing, printer, mainDexTracingResult);
         DexApplication application = converter.optimize(executorService).asDirect();
         appView.setAppInfo(appView.appInfo().rebuild(previous -> application));
       } finally {
@@ -660,8 +660,8 @@
         Set<DexProgramClass> mainDexBaseClasses =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
         // Calculate the automatic main dex list according to legacy multidex constraints.
-        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, appView).run();
-        final MainDexClasses finalMainDexClasses = mainDexClasses;
+        mainDexTracingResult = new MainDexListBuilder(mainDexBaseClasses, appView).run();
+        final MainDexTracingResult finalMainDexClasses = mainDexTracingResult;
 
         processWhyAreYouKeepingAndCheckDiscarded(
             mainDexRootSet,
@@ -772,9 +772,10 @@
                 .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
                 .build(appView.withLiveness(), removedClasses)
                 .run();
-            if (!mainDexClasses.isEmpty()) {
+            if (!mainDexTracingResult.isEmpty()) {
               // Remove types that no longer exists from the computed main dex list.
-              mainDexClasses = mainDexClasses.prunedCopy(appView.appInfo().withLiveness());
+              mainDexTracingResult =
+                  mainDexTracingResult.prunedCopy(appView.appInfo().withLiveness());
             }
 
             // Synthesize fields for triggering class initializers.
@@ -785,7 +786,7 @@
         }
 
         if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
-          IRConverter converter = new IRConverter(appView, timing, null, mainDexClasses);
+          IRConverter converter = new IRConverter(appView, timing, null, mainDexTracingResult);
 
           // 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
@@ -872,17 +873,7 @@
 
       // Add automatic main dex classes to an eventual manual list of classes.
       if (!options.mainDexKeepRules.isEmpty()) {
-        MainDexClasses finalMainDexClasses = mainDexClasses;
-        appView.setAppInfo(
-            appView
-                .appInfo()
-                .rebuild(
-                    application ->
-                        application
-                            .builder()
-                            .addToMainDexList(finalMainDexClasses.getClasses())
-                            .build()
-                            .asDirect()));
+        appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
       }
 
       // Validity checks.
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 80dd656..0dcca76 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -189,9 +189,7 @@
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     ApplicationWriter writer =
         new ApplicationWriter(
-            app,
             AppView.createForD8(AppInfo.createInitialAppInfo(app)),
-            options,
             null,
             null,
             InitClassLens.getDefault(),
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index b679a54..f128f76 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -131,7 +134,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 6510e23..5757b8c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -21,7 +24,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.ARRAYLENGTH);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index a9d754e..7993e8e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -5,15 +5,18 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -58,7 +61,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(getLoadType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 5e8c152..1c16397 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -56,7 +59,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(getStoreType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 7f6a380..e263d02 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -5,15 +5,18 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -34,8 +37,15 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    visitor.visitTypeInsn(Opcodes.CHECKCAST, lens.lookupInternalName(graphLens.lookupType(type)));
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexType rewrittenType = graphLens.lookupType(type);
+    visitor.visitTypeInsn(Opcodes.CHECKCAST, namingLens.lookupInternalName(rewrittenType));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 1163483..1eeb6b4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -5,9 +5,11 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.NumericType;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -81,7 +84,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 37255ff..2bb2444 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -6,14 +6,17 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -34,10 +37,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor,
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
       GraphLens graphLens,
       InitClassLens initClassLens,
-      NamingLens namingLens) {
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitLdcInsn(Type.getObjectType(getInternalName(graphLens, namingLens)));
   }
 
@@ -52,10 +58,11 @@
   }
 
   private String getInternalName(GraphLens graphLens, NamingLens namingLens) {
-    switch (type.toShorty()) {
+    DexType rewrittenType = graphLens.lookupType(type);
+    switch (rewrittenType.toShorty()) {
       case '[':
       case 'L':
-        return namingLens.lookupInternalName(graphLens.lookupType(type));
+        return namingLens.lookupInternalName(rewrittenType);
       case 'Z':
         return "java/lang/Boolean/TYPE";
       case 'B':
@@ -73,7 +80,7 @@
       case 'D':
         return "java/lang/Double/TYPE";
       default:
-        throw new Unreachable("Unexpected type in const-class: " + type);
+        throw new Unreachable("Unexpected type in const-class: " + rewrittenType);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index c5d52f2..d058eca 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -5,15 +5,18 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -33,8 +36,17 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    visitor.visitLdcInsn(handle.toAsmHandle(lens));
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexMethodHandle rewrittenHandle =
+        rewriter.rewriteDexMethodHandle(
+            handle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+    visitor.visitLdcInsn(rewrittenHandle.toAsmHandle(namingLens));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 17577b9..e59a64e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -33,8 +36,15 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    visitor.visitLdcInsn(Type.getType(type.toDescriptorString(lens)));
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexProto rewrittenType = rewriter.rewriteProto(getType());
+    visitor.visitLdcInsn(Type.getType(rewrittenType.toDescriptorString(namingLens)));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 1c5e970..e5740e7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -21,7 +24,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.ACONST_NULL);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index b4e63a9..642eeb9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -5,13 +5,16 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -58,7 +61,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     switch (type) {
       case INT:
         {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 649f756..9037875 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+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.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -44,7 +47,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitLdcInsn(string.toString());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 79e59db..bdc470f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -6,14 +6,17 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -50,7 +53,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     throw new Unreachable(
         "CfDexItemBasedConstString instructions should always be rewritten into CfConstString");
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 32c8494..3cff4ee 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -7,15 +7,18 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -55,12 +58,18 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    DexField newField = graphLens.lookupField(field);
-    DexField newDeclaringField = graphLens.lookupField(declaringField);
-    String owner = lens.lookupInternalName(newField.holder);
-    String name = lens.lookupName(newDeclaringField).toString();
-    String desc = lens.lookupDescriptor(newField.type).toString();
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexField rewrittenField = graphLens.lookupField(field);
+    DexField rewrittenDeclaringField = graphLens.lookupField(declaringField);
+    String owner = namingLens.lookupInternalName(rewrittenField.holder);
+    String name = namingLens.lookupName(rewrittenDeclaringField).toString();
+    String desc = namingLens.lookupDescriptor(rewrittenField.type).toString();
     visitor.visitFieldInsn(opcode, owner, name, desc);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 152f9e9..f392338 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -12,9 +12,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -44,7 +46,7 @@
       return Top.SINGLETON;
     }
 
-    abstract Object getTypeOpcode(NamingLens lens);
+    abstract Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
 
     public boolean isWide() {
       return false;
@@ -102,13 +104,14 @@
     }
 
     @Override
-    Object getTypeOpcode(NamingLens lens) {
-      if (type == DexItemFactory.nullValueType) {
+    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+      DexType rewrittenType = graphLens.lookupType(type);
+      if (rewrittenType == DexItemFactory.nullValueType) {
         return Opcodes.NULL;
       }
-      switch (type.toShorty()) {
+      switch (rewrittenType.toShorty()) {
         case 'L':
-          return lens.lookupInternalName(type);
+          return namingLens.lookupInternalName(rewrittenType);
         case 'I':
           return Opcodes.INTEGER;
         case 'F':
@@ -118,7 +121,7 @@
         case 'D':
           return Opcodes.DOUBLE;
         default:
-          throw new Unreachable("Unexpected value type: " + type);
+          throw new Unreachable("Unexpected value type: " + rewrittenType);
       }
     }
 
@@ -148,7 +151,7 @@
     }
 
     @Override
-    Object getTypeOpcode(NamingLens lens) {
+    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
       return Opcodes.TOP;
     }
 
@@ -171,7 +174,7 @@
     }
 
     @Override
-    Object getTypeOpcode(NamingLens lens) {
+    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
       return label.getLabel();
     }
 
@@ -190,7 +193,7 @@
     private UninitializedThis() {}
 
     @Override
-    Object getTypeOpcode(NamingLens lens) {
+    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
       return Opcodes.UNINITIALIZED_THIS;
     }
 
@@ -225,11 +228,17 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     int stackCount = computeStackCount();
-    Object[] stackTypes = computeStackTypes(stackCount, lens);
+    Object[] stackTypes = computeStackTypes(stackCount, graphLens, namingLens);
     int localsCount = computeLocalsCount();
-    Object[] localsTypes = computeLocalsTypes(localsCount, lens);
+    Object[] localsTypes = computeLocalsTypes(localsCount, graphLens, namingLens);
     visitor.visitFrame(F_NEW, localsCount, localsTypes, stackCount, stackTypes);
   }
 
@@ -237,14 +246,14 @@
     return stack.size();
   }
 
-  private Object[] computeStackTypes(int stackCount, NamingLens lens) {
+  private Object[] computeStackTypes(int stackCount, GraphLens graphLens, NamingLens namingLens) {
     assert stackCount == stack.size();
     if (stackCount == 0) {
       return null;
     }
     Object[] stackTypes = new Object[stackCount];
     for (int i = 0; i < stackCount; i++) {
-      stackTypes[i] = stack.get(i).getTypeOpcode(lens);
+      stackTypes[i] = stack.get(i).getTypeOpcode(graphLens, namingLens);
     }
     return stackTypes;
   }
@@ -266,7 +275,7 @@
     return localsCount;
   }
 
-  private Object[] computeLocalsTypes(int localsCount, NamingLens lens) {
+  private Object[] computeLocalsTypes(int localsCount, GraphLens graphLens, NamingLens namingLens) {
     if (localsCount == 0) {
       return null;
     }
@@ -275,7 +284,8 @@
     int localIndex = 0;
     for (int i = 0; i <= maxRegister; i++) {
       FrameType type = locals.get(i);
-      localsTypes[localIndex++] = type == null ? Opcodes.TOP : type.getTypeOpcode(lens);
+      localsTypes[localIndex++] =
+          type == null ? Opcodes.TOP : type.getTypeOpcode(graphLens, namingLens);
       if (type != null && type.isWide()) {
         i++;
       }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index d38c0fe..0cd79d4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -4,12 +4,15 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -46,7 +49,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index f64d79c..31497fd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -5,15 +5,18 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -71,7 +74,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 1ee17b1..47cbcad 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -5,15 +5,18 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -71,7 +74,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index edba7b4..4a37601 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -28,7 +31,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitIincInsn(var, increment);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index afcae7d..b12d10e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -6,14 +6,17 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -39,11 +42,20 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    DexField field = initClassLens.getInitClassField(clazz);
-    String owner = lens.lookupInternalName(field.holder);
-    String name = lens.lookupName(field).toString();
-    String desc = lens.lookupDescriptor(field.type).toString();
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    // We intentionally apply the graph lens first, and then the init class lens, using the fact
+    // that the init class lens maps classes in the final program to fields in the final program.
+    DexType rewrittenClass = graphLens.lookupType(clazz);
+    DexField clinitField = initClassLens.getInitClassField(rewrittenClass);
+    String owner = namingLens.lookupInternalName(clinitField.holder);
+    String name = namingLens.lookupName(clinitField).toString();
+    String desc = namingLens.lookupDescriptor(clinitField.type).toString();
     visitor.visitFieldInsn(OPCODE, owner, name, desc);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index fc26a95..c34d576 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -43,8 +46,15 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    visitor.visitTypeInsn(Opcodes.INSTANCEOF, lens.lookupInternalName(type));
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexType rewrittenType = graphLens.lookupType(getType());
+    visitor.visitTypeInsn(Opcodes.INSTANCEOF, namingLens.lookupInternalName(rewrittenType));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 16ef22f..da09f7b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -22,7 +24,13 @@
 public abstract class CfInstruction {
 
   public abstract void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens);
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor);
 
   public abstract void print(CfPrinter printer);
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 3851031..4f21538 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 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.DexProto;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -26,6 +28,7 @@
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -72,12 +75,20 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    DexMethod newMethod = graphLens.lookupMethod(method);
-    String owner = lens.lookupInternalName(newMethod.holder);
-    String name = lens.lookupName(newMethod).toString();
-    String desc = newMethod.proto.toDescriptorString(lens);
-    visitor.visitMethodInsn(opcode, owner, name, desc, itf);
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    GraphLensLookupResult lookup =
+        graphLens.lookupMethod(method, context.getReference(), getInvokeType(context));
+    DexMethod rewrittenMethod = lookup.getMethod();
+    String owner = namingLens.lookupInternalName(rewrittenMethod.holder);
+    String name = namingLens.lookupName(rewrittenMethod).toString();
+    String desc = rewrittenMethod.proto.toDescriptorString(namingLens);
+    visitor.visitMethodInsn(lookup.getType().getCfOpcode(), owner, name, desc, itf);
   }
 
   @Override
@@ -87,25 +98,46 @@
 
   @Override
   void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
-    switch (opcode) {
-      case Opcodes.INVOKEINTERFACE:
+    Type invokeType = getInvokeType(context);
+    switch (invokeType) {
+      case DIRECT:
+        registry.registerInvokeDirect(method);
+        break;
+      case INTERFACE:
         registry.registerInvokeInterface(method);
         break;
-      case Opcodes.INVOKEVIRTUAL:
-        registry.registerInvokeVirtual(method);
-        break;
-      case Opcodes.INVOKESPECIAL:
-        if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
-          registry.registerInvokeDirect(method);
-        } else if (method.holder == context.getHolderType()) {
-          registry.registerInvokeDirect(method);
-        } else {
-          registry.registerInvokeSuper(method);
-        }
-        break;
-      case Opcodes.INVOKESTATIC:
+      case STATIC:
         registry.registerInvokeStatic(method);
         break;
+      case SUPER:
+        registry.registerInvokeSuper(method);
+        break;
+      case VIRTUAL:
+        registry.registerInvokeVirtual(method);
+        break;
+      default:
+        throw new Unreachable("Unexpected invoke type " + invokeType);
+    }
+  }
+
+  private Invoke.Type getInvokeType(DexClassAndMethod context) {
+    switch (opcode) {
+      case Opcodes.INVOKEINTERFACE:
+        return Type.INTERFACE;
+
+      case Opcodes.INVOKEVIRTUAL:
+        return Type.VIRTUAL;
+
+      case Opcodes.INVOKESPECIAL:
+        if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)
+            || method.holder == context.getHolderType()) {
+          return Type.DIRECT;
+        }
+        return Type.SUPER;
+
+      case Opcodes.INVOKESTATIC:
+        return Type.STATIC;
+
       default:
         throw new Unreachable("unknown CfInvoke opcode " + opcode);
     }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index f57046b..a455305 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
@@ -14,11 +15,13 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -38,17 +41,27 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
-    List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexCallSite rewrittenCallSite = rewriter.rewriteCallSite(callSite, context);
+    DexMethodHandle bootstrapMethod = rewrittenCallSite.bootstrapMethod;
+    List<DexValue> bootstrapArgs = rewrittenCallSite.bootstrapArgs;
     Object[] bsmArgs = new Object[bootstrapArgs.size()];
     for (int i = 0; i < bootstrapArgs.size(); i++) {
-      bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i), lens);
+      bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i), namingLens);
     }
-    Handle bsmHandle = bootstrapMethod.toAsmHandle(lens);
-    DexString methodName = lens.lookupMethodName(callSite);
+    Handle bsmHandle = bootstrapMethod.toAsmHandle(namingLens);
+    DexString methodName = namingLens.lookupMethodName(rewrittenCallSite);
     visitor.visitInvokeDynamicInsn(
-        methodName.toString(), callSite.methodProto.toDescriptorString(lens), bsmHandle, bsmArgs);
+        methodName.toString(),
+        rewrittenCallSite.methodProto.toDescriptorString(namingLens),
+        bsmHandle,
+        bsmArgs);
   }
 
   private Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index 88659de..47ee09b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -5,12 +5,15 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -31,7 +34,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     throw error();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 19ba74a..b27fd00 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -4,12 +4,15 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -44,7 +47,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitLabel(getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 80bb1da..17e0ecd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -58,7 +61,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitVarInsn(getLoadType(), var);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 0f5bd4e..19a3bfe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -106,7 +109,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index ebefa78..24b580b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -4,14 +4,17 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Monitor.Type;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -32,7 +35,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(type == Type.ENTER ? Opcodes.MONITORENTER : Opcodes.MONITOREXIT);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 69a69af..33f4244 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -39,8 +42,15 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    visitor.visitMultiANewArrayInsn(lens.lookupInternalName(type), dimensions);
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexType rewrittenType = graphLens.lookupType(getType());
+    visitor.visitMultiANewArrayInsn(namingLens.lookupInternalName(rewrittenType), dimensions);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index 32ef906..88274b3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -33,7 +36,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index e0b8785..83b7e63 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -33,8 +36,15 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
-    visitor.visitTypeInsn(Opcodes.NEW, lens.lookupInternalName(type));
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexType rewrittenType = graphLens.lookupType(getType());
+    visitor.visitTypeInsn(Opcodes.NEW, namingLens.lookupInternalName(rewrittenType));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 6c3869b..3c9a746 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -6,15 +6,18 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -58,21 +61,36 @@
     }
   }
 
-  private String getElementInternalName(NamingLens lens) {
+  private String getElementInternalName(
+      DexItemFactory dexItemFactory, GraphLens graphLens, NamingLens namingLens) {
     assert !type.isPrimitiveArrayType();
-    String renamedArrayType = lens.lookupDescriptor(type).toString();
-    assert renamedArrayType.charAt(0) == '[';
-    String elementType = renamedArrayType.substring(1);
-    return DescriptorUtils.descriptorToInternalName(elementType);
+    StringBuilder renamedElementDescriptor = new StringBuilder();
+    // Intentionally starting from 1 to get the element descriptor.
+    int numberOfLeadingSquareBrackets = getType().getNumberOfLeadingSquareBrackets();
+    for (int i = 1; i < numberOfLeadingSquareBrackets; i++) {
+      renamedElementDescriptor.append("[");
+    }
+    DexType baseType = getType().toBaseType(dexItemFactory);
+    DexType rewrittenBaseType = graphLens.lookupType(baseType);
+    renamedElementDescriptor.append(
+        namingLens.lookupDescriptor(rewrittenBaseType).toSourceString());
+    return DescriptorUtils.descriptorToInternalName(renamedElementDescriptor.toString());
   }
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     if (type.isPrimitiveArrayType()) {
       visitor.visitIntInsn(Opcodes.NEWARRAY, getPrimitiveTypeCode());
     } else {
-      visitor.visitTypeInsn(Opcodes.ANEWARRAY, getElementInternalName(lens));
+      visitor.visitTypeInsn(
+          Opcodes.ANEWARRAY, getElementInternalName(dexItemFactory, graphLens, namingLens));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 18a2ae6..e2b6044 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,12 +4,15 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -20,7 +23,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.NOP);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index 405fb42..ce3ab3b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -43,7 +46,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(this.getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 70a3f07..e30be70 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -28,7 +31,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitLineNumber(position.line, label.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index d66d6cf..c5198d1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -55,7 +58,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(getOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 83b5e60..8f7bb37 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -4,12 +4,15 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -25,7 +28,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.RETURN);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index a4ecce6..a7a39b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -6,14 +6,17 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -77,7 +80,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(opcode.opcode);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index c3ce5db..d46d3a3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -5,14 +5,17 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -58,7 +61,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitVarInsn(getStoreType(), var);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 3a3c72e..edc5244 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -71,7 +74,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     Label[] labels = new Label[targets.size()];
     for (int i = 0; i < targets.size(); i++) {
       labels[i] = targets.get(i).getLabel();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index 3b49cb0..aaa7280 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
@@ -26,7 +29,13 @@
 
   @Override
   public void write(
-      MethodVisitor visitor, GraphLens graphLens, InitClassLens initClassLens, NamingLens lens) {
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
     visitor.visitInsn(Opcodes.ATHROW);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index 569b358..6ca2dc5 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -5,9 +5,12 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 
 public class CheckCast extends Format21c<DexType> {
 
@@ -39,8 +42,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index 2f8f7db..ec8a13a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -5,9 +5,12 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 
 public class ConstClass extends Format21c<DexType> {
 
@@ -39,8 +42,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
index 8518946..75fc2dc 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -6,11 +6,14 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -64,17 +67,33 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
-    int index = BBBB.getOffset(mapping);
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexMethodHandle rewritten =
+        rewriter.rewriteDexMethodHandle(
+            getMethodHandle(), MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+    int index = rewritten.getOffset(mapping);
     if (index != (index & 0xffff)) {
       throw new InternalCompilerError("MethodHandle-index overflow.");
     }
-    super.write(dest, mapping);
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getMethodHandle().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexMethodHandle rewritten =
+        rewriter.rewriteDexMethodHandle(
+            getMethodHandle(), MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
index 8eb5443..4767025 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -6,10 +6,13 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -62,17 +65,29 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
-    int index = BBBB.getOffset(mapping);
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexProto rewritten = rewriter.rewriteProto(getMethodType());
+    int index = rewritten.getOffset(mapping);
     if (index != (index & 0xffff)) {
       throw new InternalCompilerError("MethodType-index overflow.");
     }
-    super.write(dest, mapping);
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getMethodType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexProto rewritten = rewriter.rewriteProto(getMethodType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstString.java b/src/main/java/com/android/tools/r8/code/ConstString.java
index 261016b..8bccec9 100644
--- a/src/main/java/com/android/tools/r8/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/code/ConstString.java
@@ -6,9 +6,12 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -31,7 +34,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     getString().collectIndexedItems(indexedItems);
   }
 
@@ -71,12 +78,17 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     int index = BBBB.getOffset(mapping);
     if (index != (index & 0xffff)) {
       throw new InternalCompilerError("String-index overflow.");
     }
-    super.write(dest, mapping);
+    super.write(dest, context, graphLens, mapping, rewriter);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/DexInitClass.java b/src/main/java/com/android/tools/r8/code/DexInitClass.java
index 20f2047..52cd9e1 100644
--- a/src/main/java/com/android/tools/r8/code/DexInitClass.java
+++ b/src/main/java/com/android/tools/r8/code/DexInitClass.java
@@ -8,10 +8,13 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.FieldMemberType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -36,9 +39,16 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    DexField field = indexedItems.getInitClassLens().getInitClassField(clazz);
-    field.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    // We intentionally apply the graph lens first, and then the init class lens, using the fact
+    // that the init class lens maps classes in the final program to fields in the final program.
+    DexType rewrittenClass = graphLens.lookupType(clazz);
+    DexField clinitField = indexedItems.getInitClassLens().getInitClassField(rewrittenClass);
+    clinitField.collectIndexedItems(indexedItems);
   }
 
   @Override
@@ -91,10 +101,18 @@
   }
 
   @Override
-  public void write(ShortBuffer buffer, ObjectToOffsetMapping mapping) {
-    DexField field = mapping.getClinitField(clazz);
-    writeFirst(dest, buffer, getOpcode(field));
-    write16BitReference(field, buffer, mapping);
+  public void write(
+      ShortBuffer buffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    // We intentionally apply the graph lens first, and then the init class lens, using the fact
+    // that the init class lens maps classes in the final program to fields in the final program.
+    DexType rewrittenClass = graphLens.lookupType(clazz);
+    DexField clinitField = mapping.getClinitField(rewrittenClass);
+    writeFirst(dest, buffer, getOpcode(clinitField));
+    write16BitReference(clinitField, buffer, mapping);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
index 1fb6e8c..c50bba8 100644
--- a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
@@ -6,9 +6,12 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import java.nio.ShortBuffer;
@@ -35,7 +38,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     getItem().collectIndexedItems(indexedItems);
   }
 
@@ -78,7 +85,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     throw new Unreachable(
         "DexItemBasedConstString instructions should always be rewritten into ConstString");
   }
diff --git a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
index 36338f9..20d412f 100644
--- a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
+++ b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
@@ -42,12 +45,17 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(3, dest);  // Pseudo-opcode = 0x0300
     write16BitValue(element_width, dest);
     write32BitValue(size, dest);
-    for (int i = 0; i < data.length; i++) {
-      write16BitValue(data[i], dest);
+    for (short datum : data) {
+      write16BitValue(datum, dest);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArray.java b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
index 1932e16..5198876 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
@@ -5,8 +5,13 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class FilledNewArray extends Format35c<DexType> {
 
@@ -38,8 +43,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   public DexType getType() {
@@ -55,4 +65,17 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    writeFirst(A, G, dest);
+    write16BitReference(rewritten, dest, mapping);
+    write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
index 7fc2adc..de651f5 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
@@ -5,8 +5,13 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class FilledNewArrayRange extends Format3rc<DexType> {
 
@@ -38,8 +43,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   public DexType getType() {
@@ -55,4 +65,17 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
+    write16BitValue(CCCC, dest);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index 4c61fa3..d34ecb2 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -25,7 +28,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
   }
 
@@ -53,7 +61,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format10x.java b/src/main/java/com/android/tools/r8/code/Format10x.java
index 8eed571..6ffc866 100644
--- a/src/main/java/com/android/tools/r8/code/Format10x.java
+++ b/src/main/java/com/android/tools/r8/code/Format10x.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -20,7 +23,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
   }
 
@@ -45,7 +53,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format11n.java b/src/main/java/com/android/tools/r8/code/Format11n.java
index f2b8ac2..933beba 100644
--- a/src/main/java/com/android/tools/r8/code/Format11n.java
+++ b/src/main/java/com/android/tools/r8/code/Format11n.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -34,7 +37,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
   }
 
@@ -58,7 +66,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format11x.java b/src/main/java/com/android/tools/r8/code/Format11x.java
index 318bf04..5d627bd 100644
--- a/src/main/java/com/android/tools/r8/code/Format11x.java
+++ b/src/main/java/com/android/tools/r8/code/Format11x.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -25,7 +28,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
   }
 
@@ -53,7 +61,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format12x.java b/src/main/java/com/android/tools/r8/code/Format12x.java
index ac90623..36284b9 100644
--- a/src/main/java/com/android/tools/r8/code/Format12x.java
+++ b/src/main/java/com/android/tools/r8/code/Format12x.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -28,7 +31,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
   }
 
@@ -57,7 +65,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index f145476..42d172a 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -24,7 +27,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
     write16BitValue(AAAA, dest);
   }
@@ -53,7 +61,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format21c.java b/src/main/java/com/android/tools/r8/code/Format21c.java
index 04848f0..9118072 100644
--- a/src/main/java/com/android/tools/r8/code/Format21c.java
+++ b/src/main/java/com/android/tools/r8/code/Format21c.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
@@ -29,7 +32,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write16BitReference(BBBB, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/code/Format21h.java b/src/main/java/com/android/tools/r8/code/Format21h.java
index 221dc9e..848990d 100644
--- a/src/main/java/com/android/tools/r8/code/Format21h.java
+++ b/src/main/java/com/android/tools/r8/code/Format21h.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format21h extends Base2Format {
@@ -28,7 +31,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write16BitValue(BBBB, dest);
   }
@@ -48,7 +56,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format21s.java b/src/main/java/com/android/tools/r8/code/Format21s.java
index a9d7dc1..31c4bc9 100644
--- a/src/main/java/com/android/tools/r8/code/Format21s.java
+++ b/src/main/java/com/android/tools/r8/code/Format21s.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
@@ -30,7 +33,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write16BitValue(BBBB, dest);
   }
@@ -60,7 +68,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index d89b029..5e52bac 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -5,10 +5,13 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -32,7 +35,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write16BitValue(BBBB, dest);
   }
@@ -78,7 +86,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22b.java b/src/main/java/com/android/tools/r8/code/Format22b.java
index 3952eb7..48955dc 100644
--- a/src/main/java/com/android/tools/r8/code/Format22b.java
+++ b/src/main/java/com/android/tools/r8/code/Format22b.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
@@ -34,7 +37,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write16BitValue(combineBytes(CC, BB), dest);
   }
@@ -65,7 +73,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22c.java b/src/main/java/com/android/tools/r8/code/Format22c.java
index 751ef61..0ff7841 100644
--- a/src/main/java/com/android/tools/r8/code/Format22c.java
+++ b/src/main/java/com/android/tools/r8/code/Format22c.java
@@ -4,13 +4,12 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
-import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
 
-public abstract class Format22c<T extends IndexedDexItem> extends Base2Format {
+public abstract class Format22c<T extends DexReference> extends Base2Format {
 
   public final byte A;
   public final byte B;
@@ -33,12 +32,6 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
-    writeFirst(B, A, dest);
-    write16BitReference(CCCC, dest, mapping);
-  }
-
-  @Override
   public final int hashCode() {
     return ((CCCC.hashCode() << 8) | (A << 4) | B) ^ getClass().hashCode();
   }
diff --git a/src/main/java/com/android/tools/r8/code/Format22s.java b/src/main/java/com/android/tools/r8/code/Format22s.java
index 25c51d6..1b9aeb4 100644
--- a/src/main/java/com/android/tools/r8/code/Format22s.java
+++ b/src/main/java/com/android/tools/r8/code/Format22s.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
@@ -34,7 +37,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
     write16BitValue(CCCC, dest);
   }
@@ -65,7 +73,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 6505e5c..4e75d23 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -5,10 +5,13 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -36,7 +39,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
     write16BitValue(CCCC, dest);
   }
@@ -82,7 +90,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format22x.java b/src/main/java/com/android/tools/r8/code/Format22x.java
index 5c1b2c4..901af1e 100644
--- a/src/main/java/com/android/tools/r8/code/Format22x.java
+++ b/src/main/java/com/android/tools/r8/code/Format22x.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -29,7 +32,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write16BitValue(BBBB, dest);
   }
@@ -59,7 +67,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format23x.java b/src/main/java/com/android/tools/r8/code/Format23x.java
index 739b66f..e9d030b 100644
--- a/src/main/java/com/android/tools/r8/code/Format23x.java
+++ b/src/main/java/com/android/tools/r8/code/Format23x.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -33,7 +36,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write16BitValue(combineBytes(CC, BB), dest);
   }
@@ -63,7 +71,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index bfcf0d3..dcf5064 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -23,7 +26,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
     write32BitValue(AAAAAAAA, dest);
   }
@@ -52,7 +60,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format31c.java b/src/main/java/com/android/tools/r8/code/Format31c.java
index ead4920..023aeac 100644
--- a/src/main/java/com/android/tools/r8/code/Format31c.java
+++ b/src/main/java/com/android/tools/r8/code/Format31c.java
@@ -7,8 +7,11 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
@@ -32,7 +35,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write32BitReference(BBBBBBBB, dest, mapping);
   }
@@ -58,7 +66,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     BBBBBBBB.collectIndexedItems(indexedItems);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Format31i.java b/src/main/java/com/android/tools/r8/code/Format31i.java
index 5e55ca4..843319b 100644
--- a/src/main/java/com/android/tools/r8/code/Format31i.java
+++ b/src/main/java/com/android/tools/r8/code/Format31i.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -28,7 +31,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write32BitValue(BBBBBBBB, dest);
   }
@@ -53,7 +61,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format31t.java b/src/main/java/com/android/tools/r8/code/Format31t.java
index d650fed..9c7fdfd 100644
--- a/src/main/java/com/android/tools/r8/code/Format31t.java
+++ b/src/main/java/com/android/tools/r8/code/Format31t.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -28,7 +31,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     assert (getOffset() + BBBBBBBB) % 2 == 0;
     write32BitValue(BBBBBBBB, dest);
@@ -68,7 +76,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format32x.java b/src/main/java/com/android/tools/r8/code/Format32x.java
index a31a494..34ecf4f 100644
--- a/src/main/java/com/android/tools/r8/code/Format32x.java
+++ b/src/main/java/com/android/tools/r8/code/Format32x.java
@@ -6,7 +6,10 @@
 import static com.android.tools.r8.dex.Constants.U16BIT_MAX;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -30,7 +33,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
     write16BitValue(AAAA, dest);
     write16BitValue(BBBB, dest);
@@ -61,7 +69,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Format35c.java b/src/main/java/com/android/tools/r8/code/Format35c.java
index fba7d0d..8c2cb22 100644
--- a/src/main/java/com/android/tools/r8/code/Format35c.java
+++ b/src/main/java/com/android/tools/r8/code/Format35c.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
-import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
 
 public abstract class Format35c<T extends IndexedDexItem> extends Base3Format {
@@ -51,13 +49,6 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
-    writeFirst(A, G, dest);
-    write16BitReference(BBBB, dest, mapping);
-    write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
-  }
-
-  @Override
   public final int hashCode() {
     return ((BBBB.hashCode() << 24) | (A << 20) | (C << 16) | (D << 12) | (E << 8) | (F << 4)
         | G) ^ getClass().hashCode();
diff --git a/src/main/java/com/android/tools/r8/code/Format3rc.java b/src/main/java/com/android/tools/r8/code/Format3rc.java
index a50a9d9..98e78cc 100644
--- a/src/main/java/com/android/tools/r8/code/Format3rc.java
+++ b/src/main/java/com/android/tools/r8/code/Format3rc.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.ClassNameMapper;
-import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
 
 public abstract class Format3rc<T extends IndexedDexItem> extends Base3Format {
@@ -37,13 +35,6 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
-    writeFirst(AA, dest);
-    write16BitReference(BBBB, dest, mapping);
-    write16BitValue(CCCC, dest);
-  }
-
-  @Override
   public final int hashCode() {
     return ((CCCC << 24) | (BBBB.hashCode() << 4) | AA) ^ getClass().hashCode();
   }
diff --git a/src/main/java/com/android/tools/r8/code/Format45cc.java b/src/main/java/com/android/tools/r8/code/Format45cc.java
index f5123cf..f6820c8 100644
--- a/src/main/java/com/android/tools/r8/code/Format45cc.java
+++ b/src/main/java/com/android/tools/r8/code/Format45cc.java
@@ -8,8 +8,13 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -87,17 +92,36 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBB.collectIndexedItems(indexedItems);
-    HHHH.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    GraphLensLookupResult lookup =
+        graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
+    assert lookup.getType() == Type.POLYMORPHIC;
+    lookup.getMethod().collectIndexedItems(indexedItems);
+
+    DexProto rewrittenProto = rewriter.rewriteProto(getProto());
+    rewrittenProto.collectIndexedItems(indexedItems);
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    GraphLensLookupResult lookup =
+        graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
+    assert lookup.getType() == Type.POLYMORPHIC;
     writeFirst(A, G, dest);
-    write16BitReference(BBBB, dest, mapping);
+    write16BitReference(lookup.getMethod(), dest, mapping);
     write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
-    write16BitReference(HHHH, dest, mapping);
+
+    DexProto rewrittenProto = rewriter.rewriteProto(getProto());
+    write16BitReference(rewrittenProto, dest, mapping);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format4rcc.java b/src/main/java/com/android/tools/r8/code/Format4rcc.java
index 0ec2780..64bbc1e 100644
--- a/src/main/java/com/android/tools/r8/code/Format4rcc.java
+++ b/src/main/java/com/android/tools/r8/code/Format4rcc.java
@@ -7,8 +7,13 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
@@ -40,11 +45,21 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    GraphLensLookupResult lookup =
+        graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
+    assert lookup.getType() == Type.POLYMORPHIC;
     writeFirst(AA, dest);
-    write16BitReference(BBBB, dest, mapping);
+    write16BitReference(lookup.getMethod(), dest, mapping);
     write16BitValue(CCCC, dest);
-    write16BitReference(HHHH, dest, mapping);
+
+    DexProto rewrittenProto = rewriter.rewriteProto(getProto());
+    write16BitReference(rewrittenProto, dest, mapping);
   }
 
   @Override
@@ -93,9 +108,18 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    BBBB.collectIndexedItems(indexedItems);
-    HHHH.collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    GraphLensLookupResult lookup =
+        graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
+    assert lookup.getType() == Type.POLYMORPHIC;
+    lookup.getMethod().collectIndexedItems(indexedItems);
+
+    DexProto rewrittenProto = rewriter.rewriteProto(getProto());
+    rewrittenProto.collectIndexedItems(indexedItems);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format51l.java b/src/main/java/com/android/tools/r8/code/Format51l.java
index 0e9ad93..face456 100644
--- a/src/main/java/com/android/tools/r8/code/Format51l.java
+++ b/src/main/java/com/android/tools/r8/code/Format51l.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
 
@@ -28,7 +31,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
     write64BitValue(BBBBBBBBBBBBBBBB, dest);
   }
@@ -53,7 +61,11 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     // No references.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/IgetOrIput.java b/src/main/java/com/android/tools/r8/code/IgetOrIput.java
index 7800ace..33621e5 100644
--- a/src/main/java/com/android/tools/r8/code/IgetOrIput.java
+++ b/src/main/java/com/android/tools/r8/code/IgetOrIput.java
@@ -5,6 +5,11 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public abstract class IgetOrIput extends Format22c<DexField> {
 
@@ -17,12 +22,29 @@
   }
 
   @Override
-  public final void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getField().collectIndexedItems(indexedItems);
+  public final void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexField rewritten = graphLens.lookupField(getField());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
   public final DexField getField() {
     return CCCC;
   }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexField lookup = graphLens.lookupField(getField());
+    writeFirst(B, A, dest);
+    write16BitReference(lookup, dest, mapping);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/InstanceOf.java b/src/main/java/com/android/tools/r8/code/InstanceOf.java
index 05a9558..6c9d960 100644
--- a/src/main/java/com/android/tools/r8/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/code/InstanceOf.java
@@ -5,9 +5,14 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class InstanceOf extends Format22c<DexType> {
 
@@ -49,8 +54,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   public DexType getType() {
@@ -71,4 +81,16 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexType lookup = graphLens.lookupType(getType());
+    writeFirst(B, A, dest);
+    write16BitReference(lookup, dest, mapping);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index d8accd7..766638c 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -9,10 +9,13 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
@@ -90,7 +93,11 @@
   }
 
   protected void writeFirst(int a, int b, ShortBuffer dest) {
-    dest.put((short) (((a & 0xf) << 12) | ((b & 0xf) << 8) | (getOpcode() & 0xff)));
+    writeFirst(a, b, dest, getOpcode());
+  }
+
+  protected void writeFirst(int a, int b, ShortBuffer dest, int opcode) {
+    dest.put((short) (((a & 0xf) << 12) | ((b & 0xf) << 8) | (opcode & 0xff)));
   }
 
   protected void write16BitValue(int value, ShortBuffer dest) {
@@ -107,8 +114,8 @@
     write32BitValue((value >> 32) & 0xffffffff, dest);
   }
 
-  protected void write16BitReference(IndexedDexItem item, ShortBuffer dest,
-      ObjectToOffsetMapping mapping) {
+  protected void write16BitReference(
+      IndexedDexItem item, ShortBuffer dest, ObjectToOffsetMapping mapping) {
     int index = item.getOffset(mapping);
     assert index == (index & 0xffff);
     write16BitValue(index, dest);
@@ -309,9 +316,18 @@
     return toString(null);
   }
 
-  public abstract void write(ShortBuffer buffer, ObjectToOffsetMapping mapping);
+  public abstract void write(
+      ShortBuffer buffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter);
 
-  public abstract void collectIndexedItems(IndexedItemCollection indexedItems);
+  public abstract void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter);
 
   public boolean equals(Instruction other, BiPredicate<IndexedDexItem, IndexedDexItem> equality) {
     // In the default case, there is nothing to substitute.
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
index 72d0adb..1355f13 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
@@ -5,9 +5,14 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class InvokeCustom extends Format35c<DexCallSite> {
 
@@ -39,8 +44,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getCallSite().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexCallSite rewritten = rewriter.rewriteCallSite(getCallSite(), context);
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
@@ -62,4 +72,16 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    writeFirst(A, G, dest);
+    write16BitReference(rewriter.rewriteCallSite(getCallSite(), context), dest, mapping);
+    write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
index ae63c4d..2d09e4f 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
@@ -5,9 +5,14 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class InvokeCustomRange extends Format3rc<DexCallSite> {
 
@@ -39,8 +44,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getCallSite().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexCallSite rewritten = rewriter.rewriteCallSite(getCallSite(), context);
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
@@ -62,4 +72,17 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexCallSite rewritten = rewriter.rewriteCallSite(getCallSite(), context);
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
+    write16BitValue(CCCC, dest);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
index cd8c1b7..6786de8 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirect.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.DIRECT;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
index 41070dd..843d968 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeDirectRange.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.DIRECT;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
index 3e172e2..89b8a16 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterface.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.INTERFACE;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
index 2d1986c..e710b8c 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeInterfaceRange.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.INTERFACE;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/code/InvokeMethod.java
index cab1eac..c8539f3 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeMethod.java
@@ -5,6 +5,13 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public abstract class InvokeMethod extends Format35c<DexMethod> {
 
@@ -17,12 +24,34 @@
   }
 
   @Override
-  public final void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getMethod().collectIndexedItems(indexedItems);
+  public final void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexMethod rewritten =
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType()).getMethod();
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
   public final DexMethod getMethod() {
     return BBBB;
   }
+
+  public abstract Invoke.Type getInvokeType();
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    GraphLensLookupResult lookup =
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType());
+    writeFirst(A, G, dest, lookup.getType().getDexOpcode());
+    write16BitReference(lookup.getMethod(), dest, mapping);
+    write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java b/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java
index 25fad42..4e4d705 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java
@@ -5,6 +5,13 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public abstract class InvokeMethodRange extends Format3rc<DexMethod> {
 
@@ -17,12 +24,34 @@
   }
 
   @Override
-  public final void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getMethod().collectIndexedItems(indexedItems);
+  public final void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexMethod rewritten =
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType()).getMethod();
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
   public final DexMethod getMethod() {
     return BBBB;
   }
+
+  public abstract Type getInvokeType();
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    GraphLensLookupResult lookup =
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType());
+    writeFirst(AA, dest, lookup.getType().getDexOpcodeRange());
+    write16BitReference(lookup.getMethod(), dest, mapping);
+    write16BitValue(CCCC, dest);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
index 531105b..796b183 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStatic.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.STATIC;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
index 7d3ff3d..20809c9 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeStaticRange.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.STATIC;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
index 1dc8ac6..d2d6e28 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuper.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.SUPER;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
index af6ebe8..70fd726 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeSuperRange.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.SUPER;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
index 17596a4..9b9e064 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.VIRTUAL;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index c2a0d6c..a4f2e98 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public Type getInvokeType() {
+    return Type.VIRTUAL;
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
diff --git a/src/main/java/com/android/tools/r8/code/NewArray.java b/src/main/java/com/android/tools/r8/code/NewArray.java
index dfb4ccb..a0104a2 100644
--- a/src/main/java/com/android/tools/r8/code/NewArray.java
+++ b/src/main/java/com/android/tools/r8/code/NewArray.java
@@ -5,9 +5,14 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class NewArray extends Format22c<DexType> {
 
@@ -39,8 +44,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
@@ -61,4 +71,16 @@
   public boolean canThrow() {
     return true;
   }
+
+  @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexType lookup = graphLens.lookupType(getType());
+    writeFirst(B, A, dest);
+    write16BitReference(lookup, dest, mapping);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/NewInstance.java b/src/main/java/com/android/tools/r8/code/NewInstance.java
index 695ae96..da58744 100644
--- a/src/main/java/com/android/tools/r8/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/code/NewInstance.java
@@ -5,9 +5,12 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 
 public class NewInstance extends Format21c<DexType> {
 
@@ -39,8 +42,13 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getType().collectIndexedItems(indexedItems);
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
index 110f668..c7caf43 100644
--- a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
@@ -38,7 +41,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(1, dest);  // Pseudo-opcode = 0x0100
     write16BitValue(size, dest);
     write32BitValue(first_key, dest);
diff --git a/src/main/java/com/android/tools/r8/code/SgetOrSput.java b/src/main/java/com/android/tools/r8/code/SgetOrSput.java
index d04e098..1f77122 100644
--- a/src/main/java/com/android/tools/r8/code/SgetOrSput.java
+++ b/src/main/java/com/android/tools/r8/code/SgetOrSput.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 
 abstract class SgetOrSput extends Format21c<DexField> {
 
@@ -17,8 +20,13 @@
   }
 
   @Override
-  public final void collectIndexedItems(IndexedItemCollection indexedItems) {
-    getField().collectIndexedItems(indexedItems);
+  public final void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    DexField rewritten = graphLens.lookupField(getField());
+    rewritten.collectIndexedItems(indexedItems);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
index 90b88cc..b73779b 100644
--- a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
@@ -42,7 +45,12 @@
   }
 
   @Override
-  public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
     writeFirst(2, dest);  // Pseudo-opcode = 0x0200
     write16BitValue(size, dest);
     for (int i = 0; i < size; i++) {
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 5190adb..6b0d055 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -24,10 +24,12 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.JarApplicationReader;
 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.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ClassProvider;
@@ -36,7 +38,7 @@
 import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LibraryClassCollection;
-import com.android.tools.r8.utils.MainDexList;
+import com.android.tools.r8.utils.MainDexListParser;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -78,7 +80,9 @@
     return read((StringResource) null);
   }
 
-  public LazyLoadedDexApplication read(StringResource proguardMap) throws IOException {
+  public LazyLoadedDexApplication read(
+      StringResource proguardMap)
+      throws IOException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
       return read(proguardMap, executor);
@@ -87,7 +91,9 @@
     }
   }
 
-  public final LazyLoadedDexApplication read(ExecutorService executorService) throws IOException {
+  public final LazyLoadedDexApplication read(
+      ExecutorService executorService)
+      throws IOException {
     return read(
         inputApp.getProguardMapInputData(),
         executorService,
@@ -95,7 +101,9 @@
   }
 
   public final LazyLoadedDexApplication read(
-      StringResource proguardMap, ExecutorService executorService) throws IOException {
+      StringResource proguardMap,
+      ExecutorService executorService)
+      throws IOException {
     return read(
         proguardMap,
         executorService,
@@ -146,7 +154,6 @@
       //     about class descriptor.
       // TODO: try and preload less classes.
       readProguardMap(proguardMap, builder, executorService, futures);
-      readMainDexList(builder, executorService, futures);
       ClassReader classReader = new ClassReader(executorService, futures);
       JarClassFileReader jcf = classReader.readSources();
       ThreadUtils.awaitFutures(futures);
@@ -167,6 +174,38 @@
     return builder.build();
   }
 
+  public MainDexClasses readMainDexClasses(DexApplication app) {
+    MainDexClasses.Builder builder = MainDexClasses.builder();
+    if (inputApp.hasMainDexList()) {
+      for (StringResource resource : inputApp.getMainDexListResources()) {
+        addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, itemFactory));
+      }
+      addToMainDexClasses(
+          app,
+          builder,
+          inputApp.getMainDexClasses().stream()
+              .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
+              .collect(Collectors.toList()));
+    }
+    return builder.build();
+  }
+
+  private void addToMainDexClasses(
+      DexApplication app, MainDexClasses.Builder builder, Iterable<DexType> types) {
+    for (DexType type : types) {
+      DexProgramClass clazz = app.programDefinitionFor(type);
+      if (clazz != null) {
+        builder.add(clazz);
+      } else if (!options.ignoreMainDexMissingClasses) {
+        options.reporter.warning(
+            new StringDiagnostic(
+                "Application does not contain `"
+                    + type.toSourceString()
+                    + "` as referenced in main-dex-list."));
+      }
+    }
+  }
+
   private static void dumpInputToFile(AndroidApp app, Path output, InternalOptions options) {
     app.dump(output, options);
   }
@@ -220,23 +259,6 @@
             }));
   }
 
-  private void readMainDexList(DexApplication.Builder<?> builder, ExecutorService executorService,
-      List<Future<?>> futures) {
-    if (inputApp.hasMainDexList()) {
-      futures.add(executorService.submit(() -> {
-        for (StringResource resource : inputApp.getMainDexListResources()) {
-          builder.addToMainDexList(MainDexList.parseList(resource, itemFactory));
-        }
-
-        builder.addToMainDexList(
-            inputApp.getMainDexClasses()
-                .stream()
-                .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
-                .collect(Collectors.toList()));
-      }));
-    }
-  }
-
   private final class ClassReader {
     private final ExecutorService executorService;
     private final List<Future<?>> futures;
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 21b5cd4..b0f836c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -44,6 +44,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.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -69,7 +70,6 @@
 
 public class ApplicationWriter {
 
-  public final DexApplication application;
   public final AppView<?> appView;
   public final GraphLens graphLens;
   public final InitClassLens initClassLens;
@@ -146,18 +146,14 @@
   }
 
   public ApplicationWriter(
-      DexApplication application,
       AppView<?> appView,
-      InternalOptions options,
       List<Marker> markers,
       GraphLens graphLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       ProguardMapSupplier proguardMapSupplier) {
     this(
-        application,
         appView,
-        options,
         markers,
         graphLens,
         initClassLens,
@@ -167,20 +163,15 @@
   }
 
   public ApplicationWriter(
-      DexApplication application,
       AppView<?> appView,
-      InternalOptions options,
       List<Marker> markers,
       GraphLens graphLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       ProguardMapSupplier proguardMapSupplier,
       DexIndexedConsumer consumer) {
-    assert application != null;
-    this.application = application;
     this.appView = appView;
-    assert options != null;
-    this.options = options;
+    this.options = appView.options();
     this.desugaredLibraryCodeToKeep = CodeToKeep.createCodeToKeep(options, namingLens);
     this.markers = markers;
     this.graphLens = graphLens;
@@ -199,7 +190,7 @@
           options.getDexFilePerClassFileConsumer().combineSyntheticClassesWithPrimaryClass());
     } else if (!options.canUseMultidex()
         && options.mainDexKeepRules.isEmpty()
-        && application.mainDexList.isEmpty()
+        && appView.appInfo().getMainDexClasses().isEmpty()
         && options.enableMainDexListCheck) {
       distributor = new VirtualFile.MonoDexDistributor(this, options);
     } else {
@@ -215,7 +206,7 @@
    * This needs to be done after distribute but before dex string sorting.
    */
   private void encodeChecksums(Iterable<VirtualFile> files) {
-    List<DexProgramClass> classes = application.classes();
+    Collection<DexProgramClass> classes = appView.appInfo().classes();
     Reference2LongMap<DexString> inputChecksums = new Reference2LongOpenHashMap<>(classes.size());
     for (DexProgramClass clazz : classes) {
       inputChecksums.put(clazz.getType().descriptor, clazz.getChecksum());
@@ -226,12 +217,12 @@
         DexString desc = clazz.type.descriptor;
         toWrite.addChecksum(desc.toString(), inputChecksums.getLong(desc));
       }
-      file.injectString(application.dexItemFactory.createString(toWrite.toJsonString()));
+      file.injectString(appView.dexItemFactory().createString(toWrite.toJsonString()));
     }
   }
 
   public void write(ExecutorService executorService) throws IOException, ExecutionException {
-    application.timing.begin("DexApplication.write");
+    appView.appInfo().app().timing.begin("DexApplication.write");
     ProguardMapId proguardMapId = null;
     if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
       proguardMapId = proguardMapSupplier.writeProguardMap();
@@ -246,7 +237,7 @@
       }
       markerStrings = new ArrayList<>(markers.size());
       for (Marker marker : markers) {
-        markerStrings.add(application.dexItemFactory.createString(marker.toString()));
+        markerStrings.add(appView.dexItemFactory().createString(marker.toString()));
       }
     }
     try {
@@ -266,16 +257,14 @@
       }
       assert markers == null
           || markers.isEmpty()
-          || application.dexItemFactory.extractMarkers() != null;
-      assert appView == null
-          || appView.withProtoShrinker(
-              shrinker ->
-                  virtualFiles.stream().allMatch(shrinker::verifyDeadProtoTypesNotReferenced),
-              true);
+          || appView.dexItemFactory().extractMarkers() != null;
+      assert appView.withProtoShrinker(
+          shrinker -> virtualFiles.stream().allMatch(shrinker::verifyDeadProtoTypesNotReferenced),
+          true);
 
       // TODO(b/151313617): Sorting annotations mutates elements so run single threaded on main.
       SortAnnotations sortAnnotations = new SortAnnotations(namingLens);
-      application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
+      appView.appInfo().classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
 
       for (VirtualFile virtualFile : virtualFiles) {
         if (virtualFile.isEmpty()) {
@@ -305,10 +294,11 @@
                     }
                   }
                   ObjectToOffsetMapping objectMapping =
-                      virtualFile.computeMapping(application, namingLens, initClassLens);
+                      virtualFile.computeMapping(
+                          appView.appInfo(), graphLens, namingLens, initClassLens);
                   MethodToCodeObjectMapping codeMapping =
                       rewriteCodeWithJumboStrings(
-                          objectMapping, virtualFile.classes(), application);
+                          objectMapping, virtualFile.classes(), appView.appInfo().app());
                   ByteBufferResult result =
                       writeDexFile(objectMapping, codeMapping, byteBufferProvider);
                   ByteDataView data =
@@ -345,9 +335,9 @@
       // Fail if there are pending errors, e.g., the program consumers may have reported errors.
       options.reporter.failIfPendingErrors();
       // Supply info to all additional resource consumers.
-      supplyAdditionalConsumers(application, appView, graphLens, namingLens, options);
+      supplyAdditionalConsumers(appView.appInfo().app(), appView, graphLens, namingLens, options);
     } finally {
-      application.timing.end();
+      appView.appInfo().app().timing.end();
     }
   }
 
@@ -365,7 +355,7 @@
     }
     if (options.mainDexListConsumer != null) {
       ExceptionUtils.withConsumeResourceHandler(
-          options.reporter, options.mainDexListConsumer, writeMainDexList(application, namingLens));
+          options.reporter, options.mainDexListConsumer, writeMainDexList(appView, namingLens));
       ExceptionUtils.withFinishedResourceHandler(options.reporter, options.mainDexListConsumer);
     }
 
@@ -461,7 +451,7 @@
 
   private void insertAttributeAnnotations() {
     // Convert inner-class attributes to DEX annotations
-    for (DexProgramClass clazz : application.classes()) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
       EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
       List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
       if (enclosingMethod == null && innerClasses.isEmpty()) {
@@ -609,7 +599,7 @@
             provider,
             objectMapping,
             codeMapping,
-            application,
+            appView.appInfo().app(),
             options,
             namingLens,
             desugaredLibraryCodeToKeep);
@@ -624,9 +614,11 @@
         .replace('.', '/') + ".class";
   }
 
-  private static String writeMainDexList(DexApplication application, NamingLens namingLens) {
+  private static String writeMainDexList(AppView<?> appView, NamingLens namingLens) {
+    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
     StringBuilder builder = new StringBuilder();
-    List<DexType> list = new ArrayList<>(application.mainDexList);
+    List<DexType> list = new ArrayList<>(mainDexClasses.size());
+    mainDexClasses.forEach(list::add);
     list.sort(DexType::slowCompareTo);
     list.forEach(
         type -> builder.append(mapMainDexListName(type, namingLens)).append('\n'));
diff --git a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
index 037d769..b193e73 100644
--- a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
@@ -6,9 +6,11 @@
 import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.EncodedValueUtils;
 import com.android.tools.r8.utils.LebUtils;
 import com.google.common.annotations.VisibleForTesting;
@@ -93,16 +95,20 @@
   }
 
   public void putInstructions(
-      Instruction[] insns, ObjectToOffsetMapping mapping, CodeToKeep desugaredLibraryCodeToKeep) {
+      DexCode code,
+      ProgramMethod context,
+      ObjectToOffsetMapping mapping,
+      CodeToKeep desugaredLibraryCodeToKeep) {
     int size = 0;
-    for (Instruction insn : insns) {
-      size += insn.getSize();
+    Instruction[] instructions = code.instructions;
+    for (Instruction instruction : instructions) {
+      size += instruction.getSize();
     }
     ensureSpaceFor(size * Short.BYTES);
     assert byteBuffer.position() % 2 == 0;
     ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
-    for (int i = 0; i < insns.length; i++) {
-      Instruction insn = insns[i];
+    for (int i = 0; i < instructions.length; i++) {
+      Instruction insn = instructions[i];
       DexMethod method = insn.getMethod();
       DexField field = insn.getField();
       if (field != null) {
@@ -117,7 +123,8 @@
       } else if (insn.isCheckCast()) {
         desugaredLibraryCodeToKeep.recordClass(insn.asCheckCast().getType());
       }
-      insn.write(shortBuffer, mapping);
+      insn.write(
+          shortBuffer, context, mapping.getGraphLens(), mapping, mapping.getLensCodeRewriter());
     }
     byteBuffer.position(byteBuffer.position() + shortBuffer.position() * Short.BYTES);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index da90a09..393297a 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -46,6 +46,8 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramClassVisitor;
+import com.android.tools.r8.graph.ProgramDexCode;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -175,7 +177,7 @@
     assert codeMapping.verifyCodeObjects(mixedSectionOffsets.getCodes());
 
     // Sort the codes first, as their order might impact size due to alignment constraints.
-    List<DexCode> codes = sortDexCodesByClassName();
+    List<ProgramDexCode> codes = sortDexCodesByClassName();
 
     // Output the debug_info_items first, as they have no dependencies.
     dest.moveTo(layout.getCodesOffset() + sizeOfCodeItems(codes));
@@ -185,8 +187,8 @@
       // Ensure deterministic ordering of debug info by sorting consistent with the code objects.
       layout.setDebugInfosOffset(dest.align(1));
       Set<DexDebugInfo> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size());
-      for (DexCode code : codes) {
-        DexDebugInfoForWriting info = code.getDebugInfoForWriting();
+      for (ProgramDexCode code : codes) {
+        DexDebugInfoForWriting info = code.getCode().getDebugInfoForWriting();
         if (info != null && seen.add(info)) {
           writeDebugItem(info);
         }
@@ -323,18 +325,19 @@
     return true;
   }
 
-  private List<DexCode> sortDexCodesByClassName() {
-    Map<DexCode, String> codeToSignatureMap = new IdentityHashMap<>();
-    List<DexCode> codesSorted = new ArrayList<>();
+  private List<ProgramDexCode> sortDexCodesByClassName() {
+    Map<ProgramDexCode, String> codeToSignatureMap = new IdentityHashMap<>();
+    List<ProgramDexCode> codesSorted = new ArrayList<>();
     for (DexProgramClass clazz : mapping.getClasses()) {
-      clazz.forEachMethod(
+      clazz.forEachProgramMethod(
           method -> {
-            DexCode code = codeMapping.getCode(method);
-            assert code != null || method.shouldNotHaveCode();
+            DexCode code = codeMapping.getCode(method.getDefinition());
+            assert code != null || method.getDefinition().shouldNotHaveCode();
             if (code != null) {
-              codesSorted.add(code);
-              addSignaturesFromMethod(
-                  method, code, codeToSignatureMap, application.getProguardMap());
+              ProgramDexCode programCode = new ProgramDexCode(code, method);
+              codesSorted.add(programCode);
+              codeToSignatureMap.put(
+                  programCode, getKeyForDexCodeSorting(method, application.getProguardMap()));
             }
           });
     }
@@ -342,21 +345,17 @@
     return codesSorted;
   }
 
-  private static void addSignaturesFromMethod(
-      DexEncodedMethod method,
-      DexCode code,
-      Map<DexCode, String> codeToSignatureMap,
-      ClassNameMapper proguardMap) {
+  private static String getKeyForDexCodeSorting(ProgramMethod method, ClassNameMapper proguardMap) {
     Signature signature;
     String originalClassName;
     if (proguardMap != null) {
-      signature = proguardMap.originalSignatureOf(method.method);
-      originalClassName = proguardMap.originalNameOf(method.holder());
+      signature = proguardMap.originalSignatureOf(method.getReference());
+      originalClassName = proguardMap.originalNameOf(method.getHolderType());
     } else {
-      signature = MethodSignature.fromDexMethod(method.method);
-      originalClassName = method.holder().toSourceString();
+      signature = MethodSignature.fromDexMethod(method.getReference());
+      originalClassName = method.getHolderType().toSourceString();
     }
-    codeToSignatureMap.put(code, originalClassName + signature);
+    return originalClassName + signature;
   }
 
   private <T extends IndexedDexItem> void writeFixedSectionItems(
@@ -380,8 +379,8 @@
     writeItems(items, offsetSetter, writer, 1);
   }
 
-  private <T extends DexItem> void writeItems(Collection<T> items, Consumer<Integer> offsetSetter,
-      Consumer<T> writer, int alignment) {
+  private <T> void writeItems(
+      Collection<T> items, Consumer<Integer> offsetSetter, Consumer<T> writer, int alignment) {
     if (items.isEmpty()) {
       offsetSetter.accept(0);
     } else {
@@ -390,11 +389,11 @@
     }
   }
 
-  private int sizeOfCodeItems(Iterable<DexCode> codes) {
+  private int sizeOfCodeItems(Iterable<ProgramDexCode> codes) {
     int size = 0;
-    for (DexCode code : codes) {
+    for (ProgramDexCode code : codes) {
       size = alignSize(4, size);
-      size += sizeOfCodeItem(code);
+      size += sizeOfCodeItem(code.getCode());
     }
     return size;
   }
@@ -488,7 +487,11 @@
     dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping).generate());
   }
 
-  private void writeCodeItem(DexCode code) {
+  private void writeCodeItem(ProgramDexCode code) {
+    writeCodeItem(code.getCode(), code.getMethod());
+  }
+
+  private void writeCodeItem(DexCode code, ProgramMethod method) {
     mixedSectionOffsets.setOffsetFor(code, dest.align(4));
     // Fixed size header information.
     dest.putShort((short) code.registerSize);
@@ -500,7 +503,7 @@
     int insnSizeOffset = dest.position();
     dest.forward(4);
     // Write instruction stream.
-    dest.putInstructions(code.instructions, mapping, desugaredLibraryCodeToKeep);
+    dest.putInstructions(code, method, mapping, desugaredLibraryCodeToKeep);
     // Compute size and do the backward/forward dance to write the size at the beginning.
     int insnSize = dest.position() - insnSizeOffset - 4;
     dest.rewind(insnSize + 4);
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
index 709eb9d..3a88e5d 100644
--- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
+++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.dex.VirtualFile.VirtualFileCycler;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -71,7 +71,7 @@
 
     public void updateNumbersOfIds() {
       // Use a temporary VirtualFile to evaluate the number of ids in the group.
-      VirtualFile virtualFile = new VirtualFile(0, graphLens, initClassLens, namingLens);
+      VirtualFile virtualFile = new VirtualFile(0, appView, graphLens, initClassLens, namingLens);
       // Note: sort not needed.
       for (DexProgramClass clazz : members) {
         virtualFile.addClass(clazz);
@@ -158,29 +158,30 @@
 
     }
 
-    private boolean isDependingOnMainDexClass(Set<DexProgramClass> mainDexDependents,
-        DexProgramClass dexProgramClass) {
-      if (dexProgramClass == null) {
+    private boolean isDependingOnMainDexClass(
+        Set<DexProgramClass> mainDexDependents, DexProgramClass clazz) {
+      if (clazz == null) {
         return false;
       }
 
       // Think: build on one Map<dexProgramClass, Boolean> and split in a second step.
-      if (mainDexIndependents.contains(dexProgramClass)) {
+      if (mainDexIndependents.contains(clazz)) {
         return false;
       }
-      if (mainDexDependents.contains(dexProgramClass)) {
+      if (mainDexDependents.contains(clazz)) {
         return true;
       }
-      if (mainDex.classes().contains(dexProgramClass)) {
+      if (mainDex.classes().contains(clazz)) {
         return true;
       }
       boolean isDependent = false;
-      if (isDependingOnMainDexClass(mainDexDependents,
-          app.programDefinitionFor(dexProgramClass.superType))) {
+      if (isDependingOnMainDexClass(
+          mainDexDependents, appView.programDefinitionFor(clazz.superType, clazz))) {
         isDependent = true;
       } else {
-        for (DexType interfaze : dexProgramClass.interfaces.values) {
-          if (isDependingOnMainDexClass(mainDexDependents, app.programDefinitionFor(interfaze))) {
+        for (DexType interfaze : clazz.interfaces.values) {
+          if (isDependingOnMainDexClass(
+              mainDexDependents, appView.programDefinitionFor(interfaze, clazz))) {
             isDependent = true;
             break;
           }
@@ -188,38 +189,37 @@
       }
 
       if (isDependent) {
-        mainDexDependents.add(dexProgramClass);
+        mainDexDependents.add(clazz);
       } else {
-        mainDexIndependents.add(dexProgramClass);
+        mainDexIndependents.add(clazz);
       }
       return isDependent;
     }
 
-
-    private boolean isDependingOnMainDexIndependents(DexProgramClass dexProgramClass) {
-      if (dexProgramClass == null) {
+    private boolean isDependingOnMainDexIndependents(DexProgramClass clazz) {
+      if (clazz == null) {
         return false;
       }
 
       // Think: build on one Map<dexProgramClass, Boolean> and split in a second step.
-      if (independentsFromMainDexIndependents.contains(dexProgramClass)) {
+      if (independentsFromMainDexIndependents.contains(clazz)) {
         return false;
       }
-      if (dependentsOfMainDexIndependents.contains(dexProgramClass)) {
+      if (dependentsOfMainDexIndependents.contains(clazz)) {
         return true;
       }
-      if (mainDex.classes().contains(dexProgramClass)) {
+      if (mainDex.classes().contains(clazz)) {
         return false;
       }
-      if (mainDexIndependents.contains(dexProgramClass)) {
+      if (mainDexIndependents.contains(clazz)) {
         return true;
       }
       boolean isDependent = false;
-      if (isDependingOnMainDexIndependents(app.programDefinitionFor(dexProgramClass.superType))) {
+      if (isDependingOnMainDexIndependents(appView.programDefinitionFor(clazz.superType, clazz))) {
         isDependent = true;
       } else {
-        for (DexType interfaze : dexProgramClass.interfaces.values) {
-          if (isDependingOnMainDexIndependents(app.programDefinitionFor(interfaze))) {
+        for (DexType interfaze : clazz.interfaces.values) {
+          if (isDependingOnMainDexIndependents(appView.programDefinitionFor(interfaze, clazz))) {
             isDependent = true;
             break;
           }
@@ -227,9 +227,9 @@
       }
 
       if (isDependent) {
-        dependentsOfMainDexIndependents.add(dexProgramClass);
+        dependentsOfMainDexIndependents.add(clazz);
       } else {
-        independentsFromMainDexIndependents.add(dexProgramClass);
+        independentsFromMainDexIndependents.add(clazz);
       }
       return isDependent;
     }
@@ -245,12 +245,12 @@
     private final Map<DexProgramClass, Collection<DexProgramClass>> directSubClasses;
     private final Set<DexProgramClass> classes;
 
-    DirectSubClassesInfo(DexApplication app, Set<DexProgramClass> classes) {
+    DirectSubClassesInfo(AppView<?> appView, Set<DexProgramClass> classes) {
       Map<DexProgramClass, Collection<DexProgramClass>> directSubClasses = new HashMap<>();
       for (DexProgramClass clazz : classes) {
-        addDirectSubClass(app, classes, directSubClasses, clazz.superType, clazz);
+        addDirectSubClass(appView, classes, directSubClasses, clazz.superType, clazz);
         for (DexType interfaze : clazz.interfaces.values) {
-          addDirectSubClass(app, classes, directSubClasses, interfaze, clazz);
+          addDirectSubClass(appView, classes, directSubClasses, interfaze, clazz);
         }
       }
 
@@ -263,12 +263,13 @@
       return directSubClasses.getOrDefault(clazz, Collections.emptyList());
     }
 
-    private static void addDirectSubClass(DexApplication app,
+    private static void addDirectSubClass(
+        AppView<?> appView,
         Set<DexProgramClass> classes,
         Map<DexProgramClass, Collection<DexProgramClass>> directSubClasses,
         DexType superType,
         DexProgramClass clazz) {
-      DexProgramClass zuper = app.programDefinitionFor(superType);
+      DexProgramClass zuper = appView.programDefinitionFor(superType, clazz);
       // Don't bother collecting subclasses info that we won't use.
       if (zuper != null && classes.contains(zuper)) {
         Collection<DexProgramClass> subClasses =
@@ -283,7 +284,7 @@
   private final List<VirtualFile> dexes;
   private final BitSet fullDex = new BitSet();
   private final Set<DexProgramClass> classes;
-  private final DexApplication app;
+  private final AppView<?> appView;
   private int dexIndexOffset;
   private final GraphLens graphLens;
   private final InitClassLens initClassLens;
@@ -298,7 +299,7 @@
       GraphLens graphLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
-      DexApplication app,
+      AppView<?> appView,
       ExecutorService executorService) {
     this.mainDex = mainDex;
     this.dexes = dexes;
@@ -307,10 +308,10 @@
     this.graphLens = graphLens;
     this.initClassLens = initClassLens;
     this.namingLens = namingLens;
-    this.app = app;
+    this.appView = appView;
     this.executorService = executorService;
 
-    directSubClasses = new DirectSubClassesInfo(app, classes);
+    directSubClasses = new DirectSubClassesInfo(appView, classes);
   }
 
   public void distribute() {
@@ -378,7 +379,7 @@
 
   private Collection<VirtualFile> assignGroup(ClassGroup group, List<VirtualFile> dexBlackList) {
     VirtualFileCycler cycler =
-        new VirtualFileCycler(dexes, graphLens, initClassLens, namingLens, dexIndexOffset);
+        new VirtualFileCycler(dexes, appView, graphLens, initClassLens, namingLens, dexIndexOffset);
     if (group.members.isEmpty()) {
       return Collections.emptyList();
     } else if (group.canFitInOneDex()) {
@@ -427,7 +428,7 @@
 
     Collection<VirtualFile> usedDex = new ArrayList<>();
     VirtualFileCycler cycler =
-        new VirtualFileCycler(dexes, graphLens, initClassLens, namingLens, dexIndexOffset);
+        new VirtualFileCycler(dexes, appView, graphLens, initClassLens, namingLens, dexIndexOffset);
     // Don't modify input dexBlackList. Think about modifying the input collection considering this
     // is private API.
     Set<VirtualFile> currentBlackList = new HashSet<>(dexBlackList);
@@ -581,9 +582,9 @@
     group.members.add(clazz);
 
     // Check dependencies are added to the group.
-    collectGroup(classes, group, app.programDefinitionFor(clazz.superType));
+    collectGroup(classes, group, appView.programDefinitionFor(clazz.superType, clazz));
     for (DexType interfaze : clazz.interfaces.values) {
-      collectGroup(classes, group, app.programDefinitionFor(interfaze));
+      collectGroup(classes, group, appView.programDefinitionFor(interfaze, clazz));
     }
 
     // Check that dependants are added to the group.
@@ -666,11 +667,11 @@
 
   private boolean hasDirectInheritanceInCollection(DexProgramClass clazz,
       Set<DexProgramClass> collection) {
-    if (collection.contains(app.programDefinitionFor(clazz.superType))) {
+    if (collection.contains(appView.programDefinitionFor(clazz.superType, clazz))) {
       return true;
     }
     for (DexType interfaze : clazz.interfaces.values) {
-      if (collection.contains(app.programDefinitionFor(interfaze))) {
+      if (collection.contains(appView.programDefinitionFor(interfaze, clazz))) {
         return true;
       }
     }
@@ -708,13 +709,13 @@
 
   private DexProgramClass findOneRootInSetFrom(DexProgramClass searchFrom,
       Set<DexProgramClass> classSet) {
-    DexProgramClass zuper = app.programDefinitionFor(searchFrom.superType);
+    DexProgramClass zuper = appView.programDefinitionFor(searchFrom.superType, searchFrom);
     if (classSet.contains(zuper)) {
       return findOneRootInSetFrom(zuper, classSet);
     }
     for (DexType interfaceType : searchFrom.interfaces.values) {
-      DexClass interfaceClass = app.definitionFor(interfaceType);
-      if (classSet.contains(interfaceClass)) {
+      DexClass interfaceClass = appView.definitionFor(interfaceType);
+      if (interfaceClass.isProgramClass() && classSet.contains(interfaceClass.asProgramClass())) {
         return findOneRootInSetFrom((DexProgramClass) interfaceClass, classSet);
       }
     }
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 b036d96..5e31d99 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -3,12 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -21,14 +23,15 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 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.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
@@ -77,30 +80,38 @@
 
   private final DexProgramClass primaryClass;
 
-  VirtualFile(int id, GraphLens graphLens, InitClassLens initClassLens, NamingLens namingLens) {
-    this(id, graphLens, initClassLens, namingLens, null, null);
+  VirtualFile(
+      int id,
+      AppView<?> appView,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens) {
+    this(id, appView, graphLens, initClassLens, namingLens, null, null);
   }
 
   VirtualFile(
       int id,
+      AppView<?> appView,
       GraphLens graphLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       FeatureSplit featureSplit) {
-    this(id, graphLens, initClassLens, namingLens, null, featureSplit);
+    this(id, appView, graphLens, initClassLens, namingLens, null, featureSplit);
   }
 
   private VirtualFile(
       int id,
+      AppView<?> appView,
       GraphLens graphLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
       DexProgramClass primaryClass) {
-    this(id, graphLens, initClassLens, namingLens, primaryClass, null);
+    this(id, appView, graphLens, initClassLens, namingLens, primaryClass, null);
   }
 
   private VirtualFile(
       int id,
+      AppView<?> appView,
       GraphLens graphLens,
       InitClassLens initClassLens,
       NamingLens namingLens,
@@ -109,7 +120,7 @@
     this.id = id;
     this.indexedItems = new VirtualFileIndexedItemCollection(graphLens, initClassLens, namingLens);
     this.transaction =
-        new IndexedItemTransaction(indexedItems, graphLens, initClassLens, namingLens);
+        new IndexedItemTransaction(indexedItems, appView, graphLens, initClassLens, namingLens);
     this.primaryClass = primaryClass;
     this.featureSplit = featureSplit;
   }
@@ -193,10 +204,11 @@
   }
 
   public ObjectToOffsetMapping computeMapping(
-      DexApplication application, NamingLens namingLens, InitClassLens initClassLens) {
+      AppInfo appInfo, GraphLens graphLens, NamingLens namingLens, InitClassLens initClassLens) {
     assert transaction.isEmpty();
     return new ObjectToOffsetMapping(
-        application,
+        appInfo,
+        graphLens,
         namingLens,
         initClassLens,
         indexedItems.classes,
@@ -272,12 +284,12 @@
   }
 
   public abstract static class Distributor {
-    protected final DexApplication application;
+    protected final AppView<?> appView;
     protected final ApplicationWriter writer;
     protected final List<VirtualFile> virtualFiles = new ArrayList<>();
 
     Distributor(ApplicationWriter writer) {
-      this.application = writer.application;
+      this.appView = writer.appView;
       this.writer = writer;
     }
 
@@ -304,11 +316,12 @@
       HashMap<DexProgramClass, VirtualFile> files = new HashMap<>();
       Collection<DexProgramClass> synthetics = new ArrayList<>();
       // Assign dedicated virtual files for all program classes.
-      for (DexProgramClass clazz : application.classes()) {
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
         if (!combineSyntheticClassesWithPrimaryClass || clazz.getSynthesizedFrom().isEmpty()) {
           VirtualFile file =
               new VirtualFile(
                   virtualFiles.size(),
+                  writer.appView,
                   writer.graphLens,
                   writer.initClassLens,
                   writer.namingLens,
@@ -345,13 +358,15 @@
       this.options = options;
 
       // Create the primary dex file. The distribution will add more if needed.
-      mainDexFile = new VirtualFile(0, writer.graphLens, writer.initClassLens, writer.namingLens);
+      mainDexFile =
+          new VirtualFile(
+              0, writer.appView, writer.graphLens, writer.initClassLens, writer.namingLens);
       assert virtualFiles.isEmpty();
       virtualFiles.add(mainDexFile);
       addMarkers(mainDexFile);
 
-      classes = Sets.newHashSet(application.classes());
-      originalNames = computeOriginalNameMapping(classes, application.getProguardMap());
+      classes = Sets.newHashSet(appView.appInfo().classes());
+      originalNames = computeOriginalNameMapping(classes, appView.appInfo().app().getProguardMap());
     }
 
     private void addMarkers(VirtualFile virtualFile) {
@@ -364,37 +379,30 @@
     }
 
     protected void fillForMainDexList(Set<DexProgramClass> classes) {
-      if (!application.mainDexList.isEmpty()) {
-        VirtualFile mainDexFile = virtualFiles.get(0);
-        for (DexType type : application.mainDexList) {
-          DexClass clazz = application.definitionFor(type);
-          if (clazz != null && clazz.isProgramClass()) {
-            DexProgramClass programClass = (DexProgramClass) clazz;
-            mainDexFile.addClass(programClass);
-            classes.remove(programClass);
-          } else {
-            if (!options.ignoreMainDexMissingClasses) {
-              options.reporter.warning(
-                  new StringDiagnostic(
-                      "Application does not contain `"
-                          + type.toSourceString()
-                          + "` as referenced in main-dex-list."));
-            }
-          }
-          mainDexFile.commitTransaction();
-        }
-        if (Log.ENABLED) {
-          Log.info(
-              VirtualFile.class,
-              "Main dex classes: " + mainDexFile.transaction.getNumberOfClasses());
-          Log.info(
-              VirtualFile.class,
-              "Main dex methods: " + mainDexFile.transaction.getNumberOfMethods());
-          Log.info(
-              VirtualFile.class, "Main dex fields: " + mainDexFile.transaction.getNumberOfFields());
-        }
-        mainDexFile.throwIfFull(true, options.reporter);
+      MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
+      if (mainDexClasses.isEmpty()) {
+        return;
       }
+      VirtualFile mainDexFile = virtualFiles.get(0);
+      mainDexClasses.forEach(
+          type -> {
+            DexProgramClass clazz =
+                asProgramClassOrNull(appView.appInfo().definitionForWithoutExistenceAssert(type));
+            if (clazz != null) {
+              mainDexFile.addClass(clazz);
+              classes.remove(clazz);
+            }
+            mainDexFile.commitTransaction();
+          });
+      if (Log.ENABLED) {
+        Log.info(
+            VirtualFile.class, "Main dex classes: " + mainDexFile.transaction.getNumberOfClasses());
+        Log.info(
+            VirtualFile.class, "Main dex methods: " + mainDexFile.transaction.getNumberOfMethods());
+        Log.info(
+            VirtualFile.class, "Main dex fields: " + mainDexFile.transaction.getNumberOfFields());
+      }
+      mainDexFile.throwIfFull(true, options.reporter);
     }
 
     TreeSet<DexProgramClass> sortClassesByPackage(Set<DexProgramClass> classes,
@@ -437,7 +445,7 @@
       // Pull out the classes that should go into feature splits.
       Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
           options.featureSplitConfiguration.getFeatureSplitClasses(
-              classes, application.getProguardMap());
+              classes, appView.appInfo().app().getProguardMap());
       if (featureSplitClasses.size() > 0) {
         for (Set<DexProgramClass> featureClasses : featureSplitClasses.values()) {
           classes.removeAll(featureClasses);
@@ -464,6 +472,7 @@
         VirtualFile featureFile =
             new VirtualFile(
                 0,
+                writer.appView,
                 writer.graphLens,
                 writer.initClassLens,
                 writer.namingLens,
@@ -476,9 +485,9 @@
 
         new PackageSplitPopulator(
                 filesForDistribution,
+                appView,
                 featureClasses,
                 originalNames,
-                application.dexItemFactory,
                 fillStrategy,
                 0,
                 writer.graphLens,
@@ -519,7 +528,8 @@
         assert virtualFiles.size() == 1;
         // The main dex file is filtered out, so ensure at least one file for the remaining classes.
         virtualFiles.add(
-            new VirtualFile(1, writer.graphLens, writer.initClassLens, writer.namingLens));
+            new VirtualFile(
+                1, writer.appView, writer.graphLens, writer.initClassLens, writer.namingLens));
         filesForDistribution = virtualFiles.subList(1, virtualFiles.size());
         fileIndexOffset = 1;
       }
@@ -536,7 +546,7 @@
                 writer.graphLens,
                 writer.initClassLens,
                 writer.namingLens,
-                writer.application,
+                writer.appView,
                 executorService)
             .distribute();
       } else {
@@ -545,9 +555,9 @@
         classes = sortClassesByPackage(classes, originalNames);
         new PackageSplitPopulator(
                 filesForDistribution,
+                appView,
                 classes,
                 originalNames,
-                application.dexItemFactory,
                 fillStrategy,
                 fileIndexOffset,
                 writer.graphLens,
@@ -695,6 +705,7 @@
     private final GraphLens graphLens;
     private final InitClassLens initClassLens;
     private final NamingLens namingLens;
+    private final LensCodeRewriterUtils rewriter;
 
     private final Set<DexProgramClass> classes = new LinkedHashSet<>();
     private final Set<DexField> fields = new LinkedHashSet<>();
@@ -707,6 +718,7 @@
 
     private IndexedItemTransaction(
         VirtualFileIndexedItemCollection base,
+        AppView<?> appView,
         GraphLens graphLens,
         InitClassLens initClassLens,
         NamingLens namingLens) {
@@ -714,6 +726,7 @@
       this.graphLens = graphLens;
       this.initClassLens = initClassLens;
       this.namingLens = namingLens;
+      this.rewriter = new LensCodeRewriterUtils(appView, graphLens);
     }
 
     private <T extends DexItem> boolean maybeInsert(T item, Set<T> set, Set<T> baseSet) {
@@ -725,7 +738,7 @@
     }
 
     void addClassAndDependencies(DexProgramClass clazz) {
-      clazz.collectIndexedItems(this);
+      clazz.collectIndexedItems(this, graphLens, rewriter);
     }
 
     @Override
@@ -855,6 +868,7 @@
   static class VirtualFileCycler {
 
     private final List<VirtualFile> files;
+    private final AppView<?> appView;
     private final GraphLens graphLens;
     private final InitClassLens initClassLens;
     private final NamingLens namingLens;
@@ -866,11 +880,13 @@
 
     VirtualFileCycler(
         List<VirtualFile> files,
+        AppView<?> appView,
         GraphLens graphLens,
         InitClassLens initClassLens,
         NamingLens namingLens,
         int fileIndexOffset) {
       this.files = files;
+      this.appView = appView;
       this.graphLens = graphLens;
       this.initClassLens = initClassLens;
       this.namingLens = namingLens;
@@ -904,7 +920,8 @@
         return activeFiles.next();
       } else {
         VirtualFile newFile =
-            new VirtualFile(nextFileId++, graphLens, initClassLens, namingLens, featuresplit);
+            new VirtualFile(
+                nextFileId++, appView, graphLens, initClassLens, namingLens, featuresplit);
         files.add(newFile);
         allFilesCyclic = Iterators.cycle(files);
         return newFile;
@@ -936,7 +953,8 @@
 
     VirtualFile addFile() {
       VirtualFile newFile =
-          new VirtualFile(nextFileId++, graphLens, initClassLens, namingLens, featuresplit);
+          new VirtualFile(
+              nextFileId++, appView, graphLens, initClassLens, namingLens, featuresplit);
       files.add(newFile);
 
       reset();
@@ -977,9 +995,9 @@
 
     PackageSplitPopulator(
         List<VirtualFile> files,
+        AppView<?> appView,
         Set<DexProgramClass> classes,
         Map<DexProgramClass, String> originalNames,
-        DexItemFactory dexItemFactory,
         FillStrategy fillStrategy,
         int fileIndexOffset,
         GraphLens graphLens,
@@ -988,11 +1006,12 @@
         InternalOptions options) {
       this.classes = new ArrayList<>(classes);
       this.originalNames = originalNames;
-      this.dexItemFactory = dexItemFactory;
+      this.dexItemFactory = appView.dexItemFactory();
       this.fillStrategy = fillStrategy;
       this.options = options;
       this.cycler =
-          new VirtualFileCycler(files, graphLens, initClassLens, namingLens, fileIndexOffset);
+          new VirtualFileCycler(
+              files, appView, graphLens, initClassLens, namingLens, fileIndexOffset);
     }
 
     static boolean coveredByPrefix(String originalName, String currentPrefix) {
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
new file mode 100644
index 0000000..e078248
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -0,0 +1,366 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * For a concrete field, stores the contexts in which the field is accessed.
+ *
+ * <p>If the concrete field does not have any accesses, then {@link EmptyAccessContexts}.
+ *
+ * <p>If nothing is nothing about the accesses to the concrete field, then {@link
+ * UnknownAccessContexts}.
+ *
+ * <p>Otherwise, the concrete contexts in which the field is accessed is maintained by {@link
+ * ConcreteAccessContexts}. The access contexts are qualified by the field reference they access.
+ *
+ * <p>Example: If a field `int Foo.field` is accessed directly in `void Main.direct()` and
+ * indirectly via a non-rebound reference `int FooSub.field` in `void Main.indirect()`, then the
+ * collection is:
+ *
+ * <pre>
+ *   ConcreteAccessContexts {
+ *     `int Foo.field` -> { `void Main.direct()` }
+ *     `int FooSub.field` -> { `void Main.indirect()` }
+ *   }
+ * </pre>
+ */
+public abstract class AbstractAccessContexts {
+
+  abstract void flattenAccessContexts(DexField field);
+
+  abstract void forEachAccessContext(Consumer<ProgramMethod> consumer);
+
+  /**
+   * Returns true if this field is written by a method for which {@param predicate} returns true.
+   */
+  abstract boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate);
+
+  /**
+   * Returns true if this field is only written by methods for which {@param predicate} returns
+   * true.
+   */
+  abstract boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
+
+  /**
+   * Returns true if this field is written by a method in the program other than {@param method}.
+   */
+  abstract boolean isAccessedOutside(DexEncodedMethod method);
+
+  abstract int getNumberOfAccessContexts();
+
+  public boolean isBottom() {
+    return false;
+  }
+
+  public boolean isConcrete() {
+    return false;
+  }
+
+  abstract boolean isEmpty();
+
+  public ConcreteAccessContexts asConcrete() {
+    return null;
+  }
+
+  public boolean isTop() {
+    return false;
+  }
+
+  abstract AbstractAccessContexts rewrittenWithLens(
+      DexDefinitionSupplier definitions, GraphLens lens);
+
+  public static EmptyAccessContexts empty() {
+    return EmptyAccessContexts.getInstance();
+  }
+
+  public static UnknownAccessContexts unknown() {
+    return UnknownAccessContexts.getInstance();
+  }
+
+  public static class EmptyAccessContexts extends AbstractAccessContexts {
+
+    public static EmptyAccessContexts INSTANCE = new EmptyAccessContexts();
+
+    private EmptyAccessContexts() {}
+
+    public static EmptyAccessContexts getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    void flattenAccessContexts(DexField field) {
+      // Intentionally empty.
+    }
+
+    @Override
+    void forEachAccessContext(Consumer<ProgramMethod> consumer) {
+      // Intentionally empty.
+    }
+
+    @Override
+    boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+      return false;
+    }
+
+    @Override
+    boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+      return true;
+    }
+
+    @Override
+    boolean isAccessedOutside(DexEncodedMethod method) {
+      return false;
+    }
+
+    @Override
+    int getNumberOfAccessContexts() {
+      return 0;
+    }
+
+    @Override
+    public boolean isBottom() {
+      return true;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return true;
+    }
+
+    @Override
+    AbstractAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
+      return this;
+    }
+  }
+
+  public static class ConcreteAccessContexts extends AbstractAccessContexts {
+
+    private final Map<DexField, ProgramMethodSet> accessesWithContexts;
+
+    public ConcreteAccessContexts() {
+      this(new IdentityHashMap<>());
+    }
+
+    public ConcreteAccessContexts(Map<DexField, ProgramMethodSet> accessesWithContexts) {
+      this.accessesWithContexts = accessesWithContexts;
+    }
+
+    void forEachAccess(Consumer<DexField> consumer, Predicate<DexField> predicate) {
+      if (accessesWithContexts != null) {
+        accessesWithContexts.forEach(
+            (access, contexts) -> {
+              if (predicate.test(access)) {
+                consumer.accept(access);
+              }
+            });
+      }
+    }
+
+    @Override
+    void forEachAccessContext(Consumer<ProgramMethod> consumer) {
+      // There can be indirect reads and writes of the same field reference, so we need to keep
+      // track
+      // of the previously-seen indirect accesses to avoid reporting duplicates.
+      ProgramMethodSet visited = ProgramMethodSet.create();
+      if (accessesWithContexts != null) {
+        for (ProgramMethodSet encodedAccessContexts : accessesWithContexts.values()) {
+          for (ProgramMethod encodedAccessContext : encodedAccessContexts) {
+            if (visited.add(encodedAccessContext)) {
+              consumer.accept(encodedAccessContext);
+            }
+          }
+        }
+      }
+    }
+
+    Map<DexField, ProgramMethodSet> getAccessesWithContexts() {
+      return accessesWithContexts;
+    }
+
+    @Override
+    int getNumberOfAccessContexts() {
+      if (accessesWithContexts.size() == 1) {
+        return accessesWithContexts.values().iterator().next().size();
+      }
+      throw new Unreachable(
+          "Should only be querying the number of access contexts after flattening");
+    }
+
+    ProgramMethod getUniqueAccessContext() {
+      if (accessesWithContexts != null && accessesWithContexts.size() == 1) {
+        ProgramMethodSet contexts = accessesWithContexts.values().iterator().next();
+        if (contexts.size() == 1) {
+          return contexts.iterator().next();
+        }
+      }
+      return null;
+    }
+
+    @Override
+    void flattenAccessContexts(DexField field) {
+      if (accessesWithContexts != null) {
+        ProgramMethodSet flattenedAccessContexts =
+            accessesWithContexts.computeIfAbsent(field, ignore -> ProgramMethodSet.create());
+        accessesWithContexts.forEach(
+            (access, contexts) -> {
+              if (access != field) {
+                flattenedAccessContexts.addAll(contexts);
+              }
+            });
+        accessesWithContexts.clear();
+        if (!flattenedAccessContexts.isEmpty()) {
+          accessesWithContexts.put(field, flattenedAccessContexts);
+        }
+        assert accessesWithContexts.size() <= 1;
+      }
+    }
+
+    /**
+     * Returns true if this field is written by a method for which {@param predicate} returns true.
+     */
+    @Override
+    public boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+      for (ProgramMethodSet encodedWriteContexts : accessesWithContexts.values()) {
+        for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
+          if (predicate.test(encodedWriteContext)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    /**
+     * Returns true if this field is only written by methods for which {@param predicate} returns
+     * true.
+     */
+    @Override
+    public boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+      for (ProgramMethodSet encodedWriteContexts : accessesWithContexts.values()) {
+        for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
+          if (!predicate.test(encodedWriteContext)) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * Returns true if this field is written by a method in the program other than {@param method}.
+     */
+    @Override
+    public boolean isAccessedOutside(DexEncodedMethod method) {
+      for (ProgramMethodSet encodedWriteContexts : accessesWithContexts.values()) {
+        for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
+          if (encodedWriteContext.getDefinition() != method) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    @Override
+    public boolean isConcrete() {
+      return true;
+    }
+
+    @Override
+    public ConcreteAccessContexts asConcrete() {
+      return this;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return accessesWithContexts.isEmpty();
+    }
+
+    boolean recordAccess(DexField access, ProgramMethod context) {
+      return accessesWithContexts
+          .computeIfAbsent(access, ignore -> ProgramMethodSet.create())
+          .add(context);
+    }
+
+    @Override
+    ConcreteAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
+      Map<DexField, ProgramMethodSet> newAccessesWithContexts = new IdentityHashMap<>();
+      accessesWithContexts.forEach(
+          (access, contexts) -> {
+            ProgramMethodSet newContexts =
+                newAccessesWithContexts.computeIfAbsent(
+                    lens.lookupField(access), ignore -> ProgramMethodSet.create());
+            for (ProgramMethod context : contexts) {
+              newContexts.add(lens.mapProgramMethod(context, definitions));
+            }
+          });
+      return new ConcreteAccessContexts(newAccessesWithContexts);
+    }
+  }
+
+  public static class UnknownAccessContexts extends AbstractAccessContexts {
+
+    public static UnknownAccessContexts INSTANCE = new UnknownAccessContexts();
+
+    private UnknownAccessContexts() {}
+
+    public static UnknownAccessContexts getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    void flattenAccessContexts(DexField field) {
+      // Intentionally empty.
+    }
+
+    @Override
+    void forEachAccessContext(Consumer<ProgramMethod> consumer) {
+      throw new Unreachable("Should never be iterating the access contexts when they are unknown");
+    }
+
+    @Override
+    boolean isAccessedInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+      return true;
+    }
+
+    @Override
+    boolean isAccessedOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+      return false;
+    }
+
+    @Override
+    boolean isAccessedOutside(DexEncodedMethod method) {
+      return true;
+    }
+
+    @Override
+    int getNumberOfAccessContexts() {
+      throw new Unreachable(
+          "Should never be querying the number of access contexts when they are unknown");
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean isTop() {
+      return true;
+    }
+
+    @Override
+    AbstractAccessContexts rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
+      return this;
+    }
+  }
+}
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 59239db..c49eb28 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -8,6 +8,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.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collection;
@@ -16,6 +17,7 @@
 
   private final DexApplication app;
   private final DexItemFactory dexItemFactory;
+  private final MainDexClasses mainDexClasses;
   private final SyntheticItems syntheticItems;
 
   // Set when a new AppInfo replaces a previous one. All public methods should verify that the
@@ -23,23 +25,40 @@
   private final BooleanBox obsolete;
 
   public static AppInfo createInitialAppInfo(DexApplication application) {
-    return new AppInfo(application, SyntheticItems.createInitialSyntheticItems(), new BooleanBox());
+    return createInitialAppInfo(application, MainDexClasses.createEmptyMainDexClasses());
   }
 
-  public AppInfo(DexApplication application, SyntheticItems.CommittedItems committedItems) {
-    this(application, committedItems.toSyntheticItems(), new BooleanBox());
+  public static AppInfo createInitialAppInfo(
+      DexApplication application, MainDexClasses mainDexClasses) {
+    return new AppInfo(
+        application,
+        mainDexClasses,
+        SyntheticItems.createInitialSyntheticItems(),
+        new BooleanBox());
+  }
+
+  public AppInfo(
+      DexApplication application,
+      MainDexClasses mainDexClasses,
+      SyntheticItems.CommittedItems committedItems) {
+    this(application, mainDexClasses, committedItems.toSyntheticItems(), new BooleanBox());
   }
 
   // For desugaring.
   // This is a view onto the app info and is the only place the pending synthetics are shared.
   AppInfo(CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
-    this(appInfo.app, appInfo.syntheticItems, appInfo.obsolete);
+    this(appInfo.app, appInfo.mainDexClasses, appInfo.syntheticItems, appInfo.obsolete);
     assert witness != null;
   }
 
-  private AppInfo(DexApplication application, SyntheticItems syntheticItems, BooleanBox obsolete) {
+  private AppInfo(
+      DexApplication application,
+      MainDexClasses mainDexClasses,
+      SyntheticItems syntheticItems,
+      BooleanBox obsolete) {
     this.app = application;
     this.dexItemFactory = application.dexItemFactory;
+    this.mainDexClasses = mainDexClasses;
     this.syntheticItems = syntheticItems;
     this.obsolete = obsolete;
   }
@@ -76,13 +95,20 @@
     return dexItemFactory;
   }
 
+  public MainDexClasses getMainDexClasses() {
+    return mainDexClasses;
+  }
+
   public SyntheticItems getSyntheticItems() {
     return syntheticItems;
   }
 
-  public void addSynthesizedClass(DexProgramClass clazz) {
+  public void addSynthesizedClass(DexProgramClass clazz, boolean addToMainDexClasses) {
     assert checkIfObsolete();
     syntheticItems.addSyntheticClass(clazz);
+    if (addToMainDexClasses && !mainDexClasses.isEmpty()) {
+      mainDexClasses.add(clazz);
+    }
   }
 
   public Collection<DexProgramClass> synthesizedClasses() {
@@ -189,11 +215,6 @@
     return null;
   }
 
-  public boolean isInMainDexList(DexType type) {
-    assert checkIfObsolete();
-    return app.mainDexList.contains(type);
-  }
-
   public final FieldResolutionResult resolveField(DexField field, ProgramMethod context) {
     return resolveFieldOn(field.holder, field, 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 f1b0dc2..68ce78b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
@@ -46,15 +47,19 @@
   }
 
   public static AppInfoWithClassHierarchy createInitialAppInfoWithClassHierarchy(
-      DexApplication application) {
+      DexApplication application, MainDexClasses mainDexClasses) {
     return new AppInfoWithClassHierarchy(
-        application, SyntheticItems.createInitialSyntheticItems().commit(application));
+        application,
+        mainDexClasses,
+        SyntheticItems.createInitialSyntheticItems().commit(application));
   }
 
   // For AppInfoWithLiveness.
   protected AppInfoWithClassHierarchy(
-      DexApplication application, SyntheticItems.CommittedItems committedItems) {
-    super(application, committedItems);
+      DexApplication application,
+      MainDexClasses mainDexClasses,
+      SyntheticItems.CommittedItems committedItems) {
+    super(application, mainDexClasses, committedItems);
   }
 
   // For desugaring.
@@ -69,7 +74,8 @@
 
   public AppInfoWithClassHierarchy rebuild(Function<DexApplication, DexApplication> fn) {
     DexApplication application = fn.apply(app());
-    return new AppInfoWithClassHierarchy(application, getSyntheticItems().commit(application));
+    return new AppInfoWithClassHierarchy(
+        application, getMainDexClasses(), getSyntheticItems().commit(application));
   }
 
   @Override
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 d0a3643..2a181bc 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
+import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
@@ -129,8 +130,14 @@
   }
 
   public static AppView<AppInfoWithClassHierarchy> createForR8(DexApplication application) {
+    return createForR8(application, MainDexClasses.createEmptyMainDexClasses());
+  }
+
+  public static AppView<AppInfoWithClassHierarchy> createForR8(
+      DexApplication application, MainDexClasses mainDexClasses) {
     AppInfoWithClassHierarchy appInfo =
-        AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(application);
+        AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+            application, mainDexClasses);
     return new AppView<>(
         appInfo, WholeProgramOptimizations.ON, defaultPrefixRewritingMapper(appInfo));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index c40931a..b74a480 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -39,6 +40,7 @@
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -199,18 +201,21 @@
   }
 
   public void write(
-      DexEncodedMethod method,
-      MethodVisitor visitor,
-      NamingLens namingLens,
+      ProgramMethod method,
+      int classFileVersion,
       AppView<?> appView,
-      int classFileVersion) {
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     GraphLens graphLens = appView.graphLens();
     InitClassLens initClassLens = appView.initClassLens();
     InternalOptions options = appView.options();
     CfLabel parameterLabel = null;
-    if (shouldAddParameterNames(method, appView)) {
+    if (shouldAddParameterNames(method.getDefinition(), appView)) {
       parameterLabel = new CfLabel();
-      parameterLabel.write(visitor, graphLens, initClassLens, namingLens);
+      parameterLabel.write(
+          method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
     }
     for (CfInstruction instruction : instructions) {
       if (instruction instanceof CfFrame
@@ -218,7 +223,8 @@
               || (classFileVersion == V1_6 && !options.shouldKeepStackMapTable()))) {
         continue;
       }
-      instruction.write(visitor, graphLens, initClassLens, namingLens);
+      instruction.write(
+          method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
@@ -227,19 +233,21 @@
       Label end = tryCatch.end.getLabel();
       for (int i = 0; i < tryCatch.guards.size(); i++) {
         DexType guard = tryCatch.guards.get(i);
+        DexType rewrittenGuard = graphLens.lookupType(guard);
         Label target = tryCatch.targets.get(i).getLabel();
         visitor.visitTryCatchBlock(
             start,
             end,
             target,
-            guard == options.itemFactory.throwableType
+            rewrittenGuard == options.itemFactory.throwableType
                 ? null
-                : namingLens.lookupInternalName(guard));
+                : namingLens.lookupInternalName(rewrittenGuard));
       }
     }
     if (parameterLabel != null) {
       assert localVariables.isEmpty();
-      for (Entry<Integer, DebugLocalInfo> entry : method.getParameterInfo().entrySet()) {
+      Map<Integer, DebugLocalInfo> parameterInfo = method.getDefinition().getParameterInfo();
+      for (Entry<Integer, DebugLocalInfo> entry : parameterInfo.entrySet()) {
         writeLocalVariableEntry(
             visitor, namingLens, entry.getValue(), parameterLabel, parameterLabel, entry.getKey());
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index d6d69a6..67870ab 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -14,20 +14,15 @@
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 public abstract class DexApplication {
 
   public final ImmutableList<DataResourceProvider> dataResourceProviders;
 
-  public final ImmutableSet<DexType> mainDexList;
-
   private final ClassNameMapper proguardMap;
 
   public final Timing timing;
@@ -42,13 +37,11 @@
   DexApplication(
       ClassNameMapper proguardMap,
       ImmutableList<DataResourceProvider> dataResourceProviders,
-      ImmutableSet<DexType> mainDexList,
       InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
     this.proguardMap = proguardMap;
     this.dataResourceProviders = dataResourceProviders;
-    this.mainDexList = mainDexList;
     this.options = options;
     this.dexItemFactory = options.itemFactory;
     this.highestSortingString = highestSortingString;
@@ -57,6 +50,22 @@
 
   public abstract Builder<?> builder();
 
+  public DexDefinitionSupplier getDefinitionsSupplier(
+      SyntheticDefinitionsProvider syntheticDefinitionsProvider) {
+    DexApplication self = this;
+    return new DexDefinitionSupplier() {
+      @Override
+      public DexClass definitionFor(DexType type) {
+        return syntheticDefinitionsProvider.definitionFor(type, self::definitionFor);
+      }
+
+      @Override
+      public DexItemFactory dexItemFactory() {
+        return self.dexItemFactory;
+      }
+    };
+  }
+
   // Reorder classes randomly. Note that the order of classes in program or library
   // class collections should not matter for compilation of valid code and when running
   // with assertions enabled we reorder the classes randomly to catch possible issues.
@@ -133,7 +142,6 @@
     final Timing timing;
 
     DexString highestSortingString;
-    final Set<DexType> mainDexList = Sets.newIdentityHashSet();
     private final Collection<DexProgramClass> synthesizedClasses;
 
     public Builder(InternalOptions options, Timing timing) {
@@ -153,7 +161,6 @@
       highestSortingString = application.highestSortingString;
       options = application.options;
       dexItemFactory = application.dexItemFactory;
-      mainDexList.addAll(application.mainDexList);
       synthesizedClasses = new ArrayList<>();
     }
 
@@ -185,14 +192,10 @@
       return self();
     }
 
-    public synchronized T addSynthesizedClass(
-        DexProgramClass synthesizedClass, boolean addToMainDexList) {
+    public synchronized T addSynthesizedClass(DexProgramClass synthesizedClass) {
       assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
       addProgramClass(synthesizedClass);
       synthesizedClasses.add(synthesizedClass);
-      if (addToMainDexList && !mainDexList.isEmpty()) {
-        mainDexList.add(synthesizedClass.type);
-      }
       return self();
     }
 
@@ -204,20 +207,6 @@
       return synthesizedClasses;
     }
 
-    public Set<DexType> getMainDexList() {
-      return mainDexList;
-    }
-
-    public Builder<T> addToMainDexList(DexType mainDex) {
-      mainDexList.add(mainDex);
-      return this;
-    }
-
-    public Builder<T> addToMainDexList(Collection<DexType> mainDexList) {
-      this.mainDexList.addAll(mainDexList);
-      return this;
-    }
-
     public abstract DexApplication build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 6716297..b8a1e8f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -403,11 +404,15 @@
     return builder.toString();
   }
 
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
     highestSortingString = null;
     for (Instruction insn : instructions) {
       assert !insn.isDexItemBasedConstString();
-      insn.collectIndexedItems(indexedItems);
+      insn.collectIndexedItems(indexedItems, context, graphLens, rewriter);
       if (insn.isConstString()) {
         updateHighestSortingString(insn.asConstString().getString());
       } else if (insn.isConstStringJumbo()) {
@@ -415,11 +420,11 @@
       }
     }
     if (debugInfo != null) {
-      getDebugInfoForWriting().collectIndexedItems(indexedItems);
+      getDebugInfoForWriting().collectIndexedItems(indexedItems, graphLens);
     }
     if (handlers != null) {
       for (TryHandler handler : handlers) {
-        handler.collectIndexedItems(indexedItems);
+        handler.collectIndexedItems(indexedItems, graphLens);
       }
     }
   }
@@ -548,9 +553,9 @@
       return false;
     }
 
-    public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
       for (TypeAddrPair pair : pairs) {
-        pair.collectIndexedItems(indexedItems);
+        pair.collectIndexedItems(indexedItems, graphLens);
       }
     }
 
@@ -590,8 +595,9 @@
         this.addr = addr;
       }
 
-      public void collectIndexedItems(IndexedItemCollection indexedItems) {
-        type.collectIndexedItems(indexedItems);
+      public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
+        DexType rewritten = graphLens.lookupType(type);
+        rewritten.collectIndexedItems(indexedItems);
       }
 
       @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 20fd6e6..0e59082 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -13,7 +13,7 @@
 abstract public class DexDebugEvent extends DexItem {
   public static final DexDebugEvent[] EMPTY_ARRAY = {};
 
-  public void collectIndexedItems(IndexedItemCollection collection) {
+  public void collectIndexedItems(IndexedItemCollection collection, GraphLens graphLens) {
     // Empty by default.
   }
 
@@ -220,12 +220,13 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection collection) {
+    public void collectIndexedItems(IndexedItemCollection collection, GraphLens graphLens) {
       if (name != null) {
         name.collectIndexedItems(collection);
       }
       if (type != null) {
-        type.collectIndexedItems(collection);
+        DexType rewritten = graphLens.lookupType(type);
+        rewritten.collectIndexedItems(collection);
       }
       if (signature != null) {
         signature.collectIndexedItems(collection);
@@ -359,7 +360,7 @@
     }
 
     @Override
-    public void collectIndexedItems(IndexedItemCollection collection) {
+    public void collectIndexedItems(IndexedItemCollection collection, GraphLens graphLens) {
       fileName.collectIndexedItems(collection);
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index 51ad23c..ed4f2c5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -54,14 +54,14 @@
     return false;
   }
 
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
     for (DexString parameter : parameters) {
       if (parameter != null) {
         parameter.collectIndexedItems(indexedItems);
       }
     }
     for (DexDebugEvent event : events) {
-      event.collectIndexedItems(indexedItems);
+      event.collectIndexedItems(indexedItems, graphLens);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index cc91bcd..9391a83 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -31,6 +31,10 @@
     return type == context.type ? context : contextIndependentDefinitionFor(type);
   }
 
+  default DexClass definitionFor(DexType type, ProgramMethod context) {
+    return definitionFor(type, context.getHolder());
+  }
+
   /**
    * Lookup for the program definition of a type from a given context.
    *
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 4b6fd50..7683f12 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
-import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -87,14 +86,6 @@
     this.kotlinMemberInfo = kotlinMemberInfo;
   }
 
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    field.collectIndexedItems(indexedItems);
-    annotations().collectIndexedItems(indexedItems);
-    if (accessFlags.isStatic()) {
-      getStaticValue().collectIndexedItems(indexedItems);
-    }
-  }
-
   @Override
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     annotations().collectMixedSectionItems(mixedItems);
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 ce6934a..2ca1b48 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -13,24 +13,30 @@
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
 import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.code.Const;
 import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.InstanceOf;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeDirect;
 import com.android.tools.r8.code.InvokeStatic;
 import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.code.Return;
 import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.code.XorIntLit8;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MethodToCodeObjectMapping;
 import com.android.tools.r8.dex.MixedSectionCollection;
@@ -38,6 +44,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
@@ -61,6 +68,7 @@
 import com.android.tools.r8.naming.NamingLens;
 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.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
@@ -648,20 +656,6 @@
     return "Encoded method " + method;
   }
 
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
-    checkIfObsolete();
-    this.method.collectIndexedItems(indexedItems);
-    if (code != null) {
-      if (code.isDexCode()) {
-        code.asDexCode().collectIndexedItems(indexedItems);
-      } else {
-        assert false;
-      }
-    }
-    annotations().collectIndexedItems(indexedItems);
-    parameterAnnotationsList.collectIndexedItems(indexedItems);
-  }
-
   @Override
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     mixedItems.visit(this);
@@ -850,10 +844,46 @@
   }
 
   public DexCode buildEmptyThrowingDexCode() {
-    Instruction insn[] = {new Const(0, 0), new Throw(0)};
+    Instruction[] insn = {new Const(0, 0), new Throw(0)};
     return generateCodeFromTemplate(1, 0, insn);
   }
 
+  public Code buildInstanceOfCode(DexType type, boolean negate, InternalOptions options) {
+    return options.isGeneratingClassFiles()
+        ? buildInstanceOfCfCode(type, negate)
+        : buildInstanceOfDexCode(type, negate);
+  }
+
+  public CfCode buildInstanceOfCfCode(DexType type, boolean negate) {
+    CfInstruction[] instructions = new CfInstruction[3 + BooleanUtils.intValue(negate) * 2];
+    int i = 0;
+    instructions[i++] = new CfLoad(ValueType.OBJECT, 0);
+    instructions[i++] = new CfInstanceOf(type);
+    if (negate) {
+      instructions[i++] = new CfConstNumber(1, ValueType.INT);
+      instructions[i++] = new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.INT);
+    }
+    instructions[i] = new CfReturn(ValueType.INT);
+    return new CfCode(
+        method.holder,
+        1 + BooleanUtils.intValue(negate),
+        method.getArity() + 1,
+        Arrays.asList(instructions),
+        Collections.emptyList(),
+        Collections.emptyList());
+  }
+
+  public DexCode buildInstanceOfDexCode(DexType type, boolean negate) {
+    Instruction[] instructions = new Instruction[2 + BooleanUtils.intValue(negate)];
+    int i = 0;
+    instructions[i++] = new InstanceOf(0, 0, type);
+    if (negate) {
+      instructions[i++] = new XorIntLit8(0, 0, 1);
+    }
+    instructions[i] = new Return(0);
+    return generateCodeFromTemplate(1, 0, instructions);
+  }
+
   public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
     if (appView.options().isGeneratingDex()) {
       return toMethodThatLogsErrorDexCode(appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 999f261..493bea3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -31,6 +31,10 @@
     }
   }
 
+  public DexString getName() {
+    return name;
+  }
+
   public DexTypeList getParameters() {
     return proto.parameters;
   }
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 90c37ed..a285398 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
@@ -233,7 +234,8 @@
     return originKind == Kind.CF;
   }
 
-  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, GraphLens graphLens, LensCodeRewriterUtils rewriter) {
     if (indexedItems.addClass(this)) {
       type.collectIndexedItems(indexedItems);
       if (superType != null) {
@@ -254,8 +256,8 @@
       for (InnerClassAttribute attribute : getInnerClasses()) {
         attribute.collectIndexedItems(indexedItems);
       }
-      forEachField(field -> field.collectIndexedItems(indexedItems));
-      forEachMethod(method -> method.collectIndexedItems(indexedItems));
+      forEachProgramField(field -> field.collectIndexedItems(indexedItems));
+      forEachProgramMethod(method -> method.collectIndexedItems(indexedItems, graphLens, rewriter));
     }
   }
 
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 81c4dc5..fc14405 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -286,21 +286,37 @@
   // TODO(b/158159959): Remove usage of name-based identification.
   public boolean isD8R8SynthesizedClassType() {
     String name = toSourceString();
-    return name.contains(COMPANION_CLASS_NAME_SUFFIX)
-        || name.contains(ENUM_UNBOXING_UTILITY_CLASS_NAME)
+    // The synthesized classes listed here must always be unique to a program context and thus
+    // never duplicated for distinct inputs.
+    return
+    // Hygienic suffix.
+    name.contains(COMPANION_CLASS_NAME_SUFFIX)
+        // Only generated in core lib.
         || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
-        || name.contains(DISPATCH_CLASS_NAME_SUFFIX)
         || name.contains(TYPE_WRAPPER_SUFFIX)
         || name.contains(VIVIFIED_TYPE_WRAPPER_SUFFIX)
-        || name.contains(LAMBDA_CLASS_NAME_PREFIX)
-        || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
-        || name.contains(OutlineOptions.CLASS_NAME)
-        || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
-        || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)
-        || name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)
         || name.contains(DesugaredLibraryRetargeter.DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX)
-        || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME)
-        || oldSynthesizedName(name);
+        // Non-hygienic types.
+        || isSynthesizedTypeThatCouldBeDuplicated(name);
+  }
+
+  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(ENUM_UNBOXING_UTILITY_CLASS_NAME) // Global singleton.
+        || name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
+        || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
+        || name.contains(DISPATCH_CLASS_NAME_SUFFIX) // Shared on reference.
+        || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
+        || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME) // Global singleton.
+        || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME) // Global singleton.
+        || name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX) // Shared on reference.
+        || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME); // Global singleton.
   }
 
   private boolean oldSynthesizedName(String name) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index fe85d28..3295733 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
@@ -40,14 +39,12 @@
       ImmutableList<DexClasspathClass> classpathClasses,
       ImmutableList<DexLibraryClass> libraryClasses,
       ImmutableList<DataResourceProvider> dataResourceProviders,
-      ImmutableSet<DexType> mainDexList,
       InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
     super(
         proguardMap,
         dataResourceProviders,
-        mainDexList,
         options,
         highestSortingString,
         timing);
@@ -74,22 +71,6 @@
     return classpathClasses;
   }
 
-  public DexDefinitionSupplier getDefinitionsSupplier(
-      SyntheticDefinitionsProvider syntheticDefinitionsProvider) {
-    DirectMappedDexApplication self = this;
-    return new DexDefinitionSupplier() {
-      @Override
-      public DexClass definitionFor(DexType type) {
-        return syntheticDefinitionsProvider.definitionFor(type, self::definitionFor);
-      }
-
-      @Override
-      public DexItemFactory dexItemFactory() {
-        return self.dexItemFactory;
-      }
-    };
-  }
-
   @Override
   public DexClass definitionFor(DexType type) {
     assert type.isClassType() : "Cannot lookup definition for type: " + type;
@@ -256,7 +237,6 @@
           classpathClasses,
           libraryClasses,
           ImmutableList.copyOf(dataResourceProviders),
-          ImmutableSet.copyOf(mainDexList),
           options,
           highestSortingString,
           timing);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 35917f5..19ac497 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -22,6 +22,8 @@
 
   ProgramMethod getUniqueReadContext();
 
+  boolean hasKnownWriteContexts();
+
   void forEachIndirectAccess(Consumer<DexField> consumer);
 
   void forEachIndirectAccessWithContexts(BiConsumer<DexField, ProgramMethodSet> consumer);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
index 9d19406..b3f9c25 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -10,6 +10,8 @@
 /** Provides immutable access to {@link FieldAccessInfoCollectionImpl}. */
 public interface FieldAccessInfoCollection<T extends FieldAccessInfo> {
 
+  void destroyAccessContexts();
+
   void flattenAccessContexts();
 
   boolean contains(DexField field);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 6224914..c82fcf0 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -16,6 +16,11 @@
   private Map<DexField, FieldAccessInfoImpl> infos = new IdentityHashMap<>();
 
   @Override
+  public void destroyAccessContexts() {
+    infos.values().forEach(FieldAccessInfoImpl::destroyAccessContexts);
+  }
+
+  @Override
   public void flattenAccessContexts() {
     infos.values().forEach(FieldAccessInfoImpl::flattenAccessContexts);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index ae8a212..023e423 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
@@ -36,37 +37,28 @@
 
   // Maps every direct and indirect reference in a read-context to the set of methods in which that
   // reference appears.
-  private Map<DexField, ProgramMethodSet> readsWithContexts;
+  private AbstractAccessContexts readsWithContexts = AbstractAccessContexts.empty();
 
   // Maps every direct and indirect reference in a write-context to the set of methods in which that
   // reference appears.
-  private Map<DexField, ProgramMethodSet> writesWithContexts;
+  private AbstractAccessContexts writesWithContexts = AbstractAccessContexts.empty();
 
   public FieldAccessInfoImpl(DexField field) {
     this.field = field;
   }
 
+  void destroyAccessContexts() {
+    readsWithContexts = AbstractAccessContexts.unknown();
+    writesWithContexts = AbstractAccessContexts.unknown();
+  }
+
   void flattenAccessContexts() {
     flattenAccessContexts(readsWithContexts);
     flattenAccessContexts(writesWithContexts);
   }
 
-  private void flattenAccessContexts(Map<DexField, ProgramMethodSet> accessesWithContexts) {
-    if (accessesWithContexts != null) {
-      ProgramMethodSet flattenedAccessContexts =
-          accessesWithContexts.computeIfAbsent(field, ignore -> ProgramMethodSet.create());
-      accessesWithContexts.forEach(
-          (access, contexts) -> {
-            if (access != field) {
-              flattenedAccessContexts.addAll(contexts);
-            }
-          });
-      accessesWithContexts.clear();
-      if (!flattenedAccessContexts.isEmpty()) {
-        accessesWithContexts.put(field, flattenedAccessContexts);
-      }
-      assert accessesWithContexts.size() <= 1;
-    }
+  private void flattenAccessContexts(AbstractAccessContexts accessesWithContexts) {
+    accessesWithContexts.flattenAccessContexts(field);
   }
 
   @Override
@@ -81,33 +73,24 @@
 
   @Override
   public int getNumberOfReadContexts() {
-    return getNumberOfAccessContexts(readsWithContexts);
+    return readsWithContexts.getNumberOfAccessContexts();
   }
 
   @Override
   public int getNumberOfWriteContexts() {
-    return getNumberOfAccessContexts(writesWithContexts);
-  }
-
-  private int getNumberOfAccessContexts(Map<DexField, ProgramMethodSet> accessesWithContexts) {
-    if (accessesWithContexts == null) {
-      return 0;
-    }
-    if (accessesWithContexts.size() == 1) {
-      return accessesWithContexts.values().iterator().next().size();
-    }
-    throw new Unreachable("Should only be querying the number of access contexts after flattening");
+    return writesWithContexts.getNumberOfAccessContexts();
   }
 
   @Override
   public ProgramMethod getUniqueReadContext() {
-    if (readsWithContexts != null && readsWithContexts.size() == 1) {
-      ProgramMethodSet contexts = readsWithContexts.values().iterator().next();
-      if (contexts.size() == 1) {
-        return contexts.iterator().next();
-      }
-    }
-    return null;
+    return readsWithContexts.isConcrete()
+        ? readsWithContexts.asConcrete().getUniqueAccessContext()
+        : null;
+  }
+
+  @Override
+  public boolean hasKnownWriteContexts() {
+    return !writesWithContexts.isTop();
   }
 
   @Override
@@ -115,76 +98,71 @@
     // There can be indirect reads and writes of the same field reference, so we need to keep track
     // of the previously-seen indirect accesses to avoid reporting duplicates.
     Set<DexField> visited = Sets.newIdentityHashSet();
-    forEachAccessInMap(
-        readsWithContexts, access -> access != field && visited.add(access), consumer);
-    forEachAccessInMap(
-        writesWithContexts, access -> access != field && visited.add(access), consumer);
+    forEachIndirectAccess(consumer, readsWithContexts, visited);
+    forEachIndirectAccess(consumer, writesWithContexts, visited);
   }
 
-  private static void forEachAccessInMap(
-      Map<DexField, ProgramMethodSet> accessesWithContexts,
-      Predicate<DexField> predicate,
-      Consumer<DexField> consumer) {
-    if (accessesWithContexts != null) {
-      accessesWithContexts.forEach(
-          (access, contexts) -> {
-            if (predicate.test(access)) {
-              consumer.accept(access);
-            }
-          });
+  private void forEachIndirectAccess(
+      Consumer<DexField> consumer,
+      AbstractAccessContexts accessesWithContexts,
+      Set<DexField> visited) {
+    if (accessesWithContexts.isBottom()) {
+      return;
     }
+    if (accessesWithContexts.isConcrete()) {
+      accessesWithContexts
+          .asConcrete()
+          .forEachAccess(consumer, access -> access != field && visited.add(access));
+      return;
+    }
+    throw new Unreachable("Should never be iterating the indirect accesses when they are unknown");
   }
 
   @Override
   public void forEachIndirectAccessWithContexts(BiConsumer<DexField, ProgramMethodSet> consumer) {
     Map<DexField, ProgramMethodSet> indirectAccessesWithContexts = new IdentityHashMap<>();
-    extendAccessesWithContexts(
-        indirectAccessesWithContexts, access -> access != field, readsWithContexts);
-    extendAccessesWithContexts(
-        indirectAccessesWithContexts, access -> access != field, writesWithContexts);
+    addAccessesWithContextsToMap(
+        readsWithContexts, access -> access != field, indirectAccessesWithContexts);
+    addAccessesWithContextsToMap(
+        writesWithContexts, access -> access != field, indirectAccessesWithContexts);
     indirectAccessesWithContexts.forEach(consumer);
   }
 
-  private void extendAccessesWithContexts(
+  private static void addAccessesWithContextsToMap(
+      AbstractAccessContexts accessesWithContexts,
+      Predicate<DexField> predicate,
+      Map<DexField, ProgramMethodSet> out) {
+    if (accessesWithContexts.isBottom()) {
+      return;
+    }
+    if (accessesWithContexts.isConcrete()) {
+      extendAccessesWithContexts(
+          accessesWithContexts.asConcrete().getAccessesWithContexts(), predicate, out);
+      return;
+    }
+    throw new Unreachable("Should never be iterating the indirect accesses when they are unknown");
+  }
+
+  private static void extendAccessesWithContexts(
       Map<DexField, ProgramMethodSet> accessesWithContexts,
       Predicate<DexField> predicate,
-      Map<DexField, ProgramMethodSet> extension) {
-    if (extension != null) {
-      extension.forEach(
-          (access, contexts) -> {
-            if (predicate.test(access)) {
-              accessesWithContexts
-                  .computeIfAbsent(access, ignore -> ProgramMethodSet.create())
-                  .addAll(contexts);
-            }
-          });
-    }
+      Map<DexField, ProgramMethodSet> out) {
+    accessesWithContexts.forEach(
+        (access, contexts) -> {
+          if (predicate.test(access)) {
+            out.computeIfAbsent(access, ignore -> ProgramMethodSet.create()).addAll(contexts);
+          }
+        });
   }
 
   @Override
   public void forEachReadContext(Consumer<ProgramMethod> consumer) {
-    forEachAccessContext(readsWithContexts, consumer);
+    readsWithContexts.forEachAccessContext(consumer);
   }
 
   @Override
   public void forEachWriteContext(Consumer<ProgramMethod> consumer) {
-    forEachAccessContext(writesWithContexts, consumer);
-  }
-
-  private void forEachAccessContext(
-      Map<DexField, ProgramMethodSet> accessesWithContexts, Consumer<ProgramMethod> consumer) {
-    // There can be indirect reads and writes of the same field reference, so we need to keep track
-    // of the previously-seen indirect accesses to avoid reporting duplicates.
-    ProgramMethodSet visited = ProgramMethodSet.create();
-    if (accessesWithContexts != null) {
-      for (ProgramMethodSet encodedAccessContexts : accessesWithContexts.values()) {
-        for (ProgramMethod encodedAccessContext : encodedAccessContexts) {
-          if (visited.add(encodedAccessContext)) {
-            consumer.accept(encodedAccessContext);
-          }
-        }
-      }
-    }
+    writesWithContexts.forEachAccessContext(consumer);
   }
 
   @Override
@@ -199,7 +177,7 @@
   /** Returns true if this field is read by the program. */
   @Override
   public boolean isRead() {
-    return (readsWithContexts != null && !readsWithContexts.isEmpty()) || isReadFromAnnotation();
+    return !readsWithContexts.isEmpty() || isReadFromAnnotation();
   }
 
   @Override
@@ -223,7 +201,7 @@
   /** Returns true if this field is written by the program. */
   @Override
   public boolean isWritten() {
-    return writesWithContexts != null && !writesWithContexts.isEmpty();
+    return !writesWithContexts.isEmpty();
   }
 
   @Override
@@ -240,16 +218,7 @@
    */
   @Override
   public boolean isWrittenInMethodSatisfying(Predicate<ProgramMethod> predicate) {
-    if (writesWithContexts != null) {
-      for (ProgramMethodSet encodedWriteContexts : writesWithContexts.values()) {
-        for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
-          if (predicate.test(encodedWriteContext)) {
-            return true;
-          }
-        }
-      }
-    }
-    return false;
+    return writesWithContexts.isAccessedInMethodSatisfying(predicate);
   }
 
   /**
@@ -258,16 +227,7 @@
    */
   @Override
   public boolean isWrittenOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
-    if (writesWithContexts != null) {
-      for (ProgramMethodSet encodedWriteContexts : writesWithContexts.values()) {
-        for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
-          if (!predicate.test(encodedWriteContext)) {
-            return false;
-          }
-        }
-      }
-    }
-    return true;
+    return writesWithContexts.isAccessedOnlyInMethodSatisfying(predicate);
   }
 
   /**
@@ -275,71 +235,42 @@
    */
   @Override
   public boolean isWrittenOutside(DexEncodedMethod method) {
-    if (writesWithContexts != null) {
-      for (ProgramMethodSet encodedWriteContexts : writesWithContexts.values()) {
-        for (ProgramMethod encodedWriteContext : encodedWriteContexts) {
-          if (encodedWriteContext.getDefinition() != method) {
-            return true;
-          }
-        }
-      }
+    return writesWithContexts.isAccessedOutside(method);
+  }
+
+  public boolean recordRead(DexField access, ProgramMethod context) {
+    if (readsWithContexts.isBottom()) {
+      readsWithContexts = new ConcreteAccessContexts();
+    }
+    if (readsWithContexts.isConcrete()) {
+      return readsWithContexts.asConcrete().recordAccess(access, context);
     }
     return false;
   }
 
-  public boolean recordRead(DexField access, ProgramMethod context) {
-    if (readsWithContexts == null) {
-      readsWithContexts = new IdentityHashMap<>();
-    }
-    return readsWithContexts
-        .computeIfAbsent(access, ignore -> ProgramMethodSet.create())
-        .add(context);
-  }
-
   public boolean recordWrite(DexField access, ProgramMethod context) {
-    if (writesWithContexts == null) {
-      writesWithContexts = new IdentityHashMap<>();
+    if (writesWithContexts.isBottom()) {
+      writesWithContexts = new ConcreteAccessContexts();
     }
-    return writesWithContexts
-        .computeIfAbsent(access, ignore -> ProgramMethodSet.create())
-        .add(context);
+    if (writesWithContexts.isConcrete()) {
+      return writesWithContexts.asConcrete().recordAccess(access, context);
+    }
+    return false;
   }
 
   public void clearReads() {
-    readsWithContexts = null;
+    readsWithContexts = AbstractAccessContexts.empty();
   }
 
   public void clearWrites() {
-    writesWithContexts = null;
+    writesWithContexts = AbstractAccessContexts.empty();
   }
 
   public FieldAccessInfoImpl rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
     FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field));
     rewritten.flags = flags;
-    if (readsWithContexts != null) {
-      rewritten.readsWithContexts = new IdentityHashMap<>();
-      readsWithContexts.forEach(
-          (access, contexts) -> {
-            ProgramMethodSet newContexts =
-                rewritten.readsWithContexts.computeIfAbsent(
-                    lens.lookupField(access), ignore -> ProgramMethodSet.create());
-            for (ProgramMethod context : contexts) {
-              newContexts.add(lens.mapProgramMethod(context, definitions));
-            }
-          });
-    }
-    if (writesWithContexts != null) {
-      rewritten.writesWithContexts = new IdentityHashMap<>();
-      writesWithContexts.forEach(
-          (access, contexts) -> {
-            ProgramMethodSet newContexts =
-                rewritten.writesWithContexts.computeIfAbsent(
-                    lens.lookupField(access), ignore -> ProgramMethodSet.create());
-            for (ProgramMethod context : contexts) {
-              newContexts.add(lens.mapProgramMethod(context, definitions));
-            }
-          });
-    }
+    rewritten.readsWithContexts = readsWithContexts.rewrittenWithLens(definitions, lens);
+    rewritten.writesWithContexts = writesWithContexts.rewrittenWithLens(definitions, lens);
     return rewritten;
   }
 }
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 2491193..14a4e95 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.horizontalclassmerging.ClassMerger;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -408,6 +409,12 @@
         if (field.field.match(dexItemFactory.objectMembers.clinitField)) {
           continue;
         }
+
+        // TODO(b/167947782): Should be a general check to see if the field is D8/R8 synthesized.
+        if (field.toReference().name.toSourceString().equals(ClassMerger.CLASS_ID_FIELD_NAME)) {
+          continue;
+        }
+
         DexField originalField = getOriginalFieldSignature(field.field);
         assert originalFields.contains(originalField)
             : "Unable to map field `" + field.field.toSourceString() + "` back to original program";
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 1721860..ce5ff83 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -34,14 +33,12 @@
       ImmutableList<DataResourceProvider> dataResourceProviders,
       ClasspathClassCollection classpathClasses,
       LibraryClassCollection libraryClasses,
-      ImmutableSet<DexType> mainDexList,
       InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
     super(
         proguardMap,
         dataResourceProviders,
-        mainDexList,
         options,
         highestSortingString,
         timing);
@@ -236,7 +233,6 @@
           ImmutableList.copyOf(dataResourceProviders),
           classpathClasses,
           libraryClasses,
-          ImmutableSet.copyOf(mainDexList),
           options,
           highestSortingString,
           timing);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index c7edad2..949fd47 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -269,12 +269,28 @@
   @Override
   public DexEncodedMethod replaceDirectMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    for (int i = 0; i < directMethods.length; i++) {
-      DexEncodedMethod directMethod = directMethods[i];
-      if (method.match(directMethod)) {
-        DexEncodedMethod newMethod = replacement.apply(directMethod);
-        assert belongsToDirectPool(newMethod);
-        directMethods[i] = newMethod;
+    DexEncodedMethod newMethod = replaceMethod(method, replacement, directMethods);
+    assert newMethod == null || belongsToDirectPool(newMethod);
+    return newMethod;
+  }
+
+  @Override
+  public DexEncodedMethod replaceVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    DexEncodedMethod newMethod = replaceMethod(method, replacement, virtualMethods);
+    assert newMethod == null || belongsToVirtualPool(newMethod);
+    return newMethod;
+  }
+
+  private DexEncodedMethod replaceMethod(
+      DexMethod reference,
+      Function<DexEncodedMethod, DexEncodedMethod> replacement,
+      DexEncodedMethod[] methods) {
+    for (int i = 0; i < methods.length; i++) {
+      DexEncodedMethod method = methods[i];
+      if (reference.match(method)) {
+        DexEncodedMethod newMethod = replacement.apply(method);
+        methods[i] = newMethod;
         return newMethod;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index b1a8f8e..312cec2 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -212,6 +212,12 @@
     return backing.replaceDirectMethod(method, replacement);
   }
 
+  public DexEncodedMethod replaceVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    resetVirtualMethodCaches();
+    return backing.replaceVirtualMethod(method, replacement);
+  }
+
   public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     resetCaches();
     backing.replaceMethods(replacement);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 0b3f435..601306c 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -113,6 +113,9 @@
   abstract DexEncodedMethod replaceDirectMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
 
+  abstract DexEncodedMethod replaceVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
   abstract DexEncodedMethod replaceDirectMethodWithVirtualMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index 19c3cec..d95a723 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -286,13 +286,26 @@
   @Override
   DexEncodedMethod replaceDirectMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    return replaceMethod(method, replacement, this::belongsToDirectPool);
+  }
+
+  @Override
+  DexEncodedMethod replaceVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    return replaceMethod(method, replacement, this::belongsToVirtualPool);
+  }
+
+  private DexEncodedMethod replaceMethod(
+      DexMethod method,
+      Function<DexEncodedMethod, DexEncodedMethod> replacement,
+      Predicate<DexEncodedMethod> predicate) {
     Wrapper<DexMethod> key = wrap(method);
     DexEncodedMethod existing = methodMap.get(key);
-    if (existing == null || belongsToVirtualPool(existing)) {
+    if (existing == null || !predicate.test(existing)) {
       return null;
     }
     DexEncodedMethod newMethod = replacement.apply(existing);
-    assert belongsToDirectPool(newMethod);
+    assert predicate.test(newMethod);
     replace(key, newMethod);
     return newMethod;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
index 60c06cc..28ab2ca 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
@@ -16,6 +16,8 @@
   void forEachClassWithKnownAllocationSites(
       BiConsumer<DexProgramClass, Set<DexEncodedMethod>> consumer);
 
+  boolean hasInstantiatedStrictSubtype(DexProgramClass clazz);
+
   boolean isAllocationSitesKnown(DexProgramClass clazz);
 
   boolean isInstantiatedDirectly(DexProgramClass clazz);
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index fb6e5d1..fa105e6 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -93,6 +93,7 @@
   }
 
   /** True if there might exist an instantiated (strict) subtype of the given type. */
+  @Override
   public boolean hasInstantiatedStrictSubtype(DexProgramClass clazz) {
     if (instantiatedHierarchy.get(clazz.type) != null) {
       return true;
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 33f3b90..ecc7505 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.NamingLens;
 import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -23,8 +24,10 @@
   private final static int NOT_FOUND = -1;
   private final static int NOT_SET = -2;
 
+  private final GraphLens graphLens;
   private final NamingLens namingLens;
   private final InitClassLens initClassLens;
+  private final LensCodeRewriterUtils lensCodeRewriter;
 
   // Sorted collection of objects mapped to their offsets.
   private final DexProgramClass[] classes;
@@ -39,7 +42,8 @@
   private DexString firstJumboString;
 
   public ObjectToOffsetMapping(
-      DexApplication application,
+      AppInfo appInfo,
+      GraphLens graphLens,
       NamingLens namingLens,
       InitClassLens initClassLens,
       Collection<DexProgramClass> classes,
@@ -50,7 +54,7 @@
       Collection<DexString> strings,
       Collection<DexCallSite> callSites,
       Collection<DexMethodHandle> methodHandles) {
-    assert application != null;
+    assert appInfo != null;
     assert classes != null;
     assert protos != null;
     assert types != null;
@@ -60,9 +64,11 @@
     assert callSites != null;
     assert methodHandles != null;
     assert initClassLens != null;
+    this.graphLens = graphLens;
     this.namingLens = namingLens;
     this.initClassLens = initClassLens;
-    this.classes = sortClasses(application, classes, namingLens);
+    this.lensCodeRewriter = new LensCodeRewriterUtils(appInfo, graphLens);
+    this.classes = sortClasses(appInfo, classes, namingLens);
     this.protos = createSortedMap(protos, compare(namingLens), this::failOnOverflow);
     this.types = createSortedMap(types, compare(namingLens), this::failOnOverflow);
     this.methods = createSortedMap(methods, compare(namingLens), this::failOnOverflow);
@@ -114,11 +120,11 @@
 
     private final static int UNKNOWN_DEPTH = -1;
 
-    private final DexApplication application;
+    private final AppInfo appInfo;
     private final Reference2IntMap<DexProgramClass> depthOfClasses = new Reference2IntOpenHashMap<>();
 
-    ProgramClassDepthsMemoized(DexApplication application) {
-      this.application = application;
+    ProgramClassDepthsMemoized(AppInfo appInfo) {
+      this.appInfo = appInfo;
       depthOfClasses.defaultReturnValue(UNKNOWN_DEPTH);
     }
 
@@ -134,13 +140,13 @@
           maxDepth = 0;
         } else {
           maxDepth = 1;
-          DexProgramClass superClass = application.programDefinitionFor(superType);
+          DexProgramClass superClass = appInfo.programDefinitionFor(superType, programClass);
           if (superClass != null) {
             maxDepth = getDepth(superClass);
           }
         }
         for (DexType inf : programClass.interfaces.values) {
-          DexProgramClass infClass = application.programDefinitionFor(inf);
+          DexProgramClass infClass = appInfo.programDefinitionFor(inf, programClass);
           maxDepth = Math.max(maxDepth, infClass == null ? 1 : getDepth(infClass));
         }
         depth = maxDepth + 1;
@@ -152,9 +158,9 @@
   }
 
   private static DexProgramClass[] sortClasses(
-      DexApplication application, Collection<DexProgramClass> classes, NamingLens namingLens) {
+      AppInfo appInfo, Collection<DexProgramClass> classes, NamingLens namingLens) {
     // Collect classes in subtyping order, based on a sorted list of classes to start with.
-    ProgramClassDepthsMemoized classDepths = new ProgramClassDepthsMemoized(application);
+    ProgramClassDepthsMemoized classDepths = new ProgramClassDepthsMemoized(appInfo);
     List<DexProgramClass> sortedClasses =
         classes.stream()
             .sorted(
@@ -172,10 +178,18 @@
     return map == null ? Collections.emptyList() : map.keySet();
   }
 
+  public GraphLens getGraphLens() {
+    return graphLens;
+  }
+
   public NamingLens getNamingLens() {
     return namingLens;
   }
 
+  public LensCodeRewriterUtils getLensCodeRewriter() {
+    return lensCodeRewriter;
+  }
+
   public Collection<DexMethod> getMethods() {
     return keysOrEmpty(methods);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java b/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
new file mode 100644
index 0000000..93e42d1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+public class ProgramDexCode {
+
+  private final DexCode code;
+  private final ProgramMethod method;
+
+  public ProgramDexCode(DexCode code, ProgramMethod method) {
+    this.code = code;
+    this.method = method;
+  }
+
+  public DexCode getCode() {
+    return code;
+  }
+
+  public ProgramMethod getMethod() {
+    return method;
+  }
+}
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 3069f4e..7721c67 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.IndexedItemCollection;
+
 public class ProgramField extends DexClassAndField
     implements ProgramMember<DexEncodedField, DexField> {
 
@@ -11,6 +13,15 @@
     super(holder, field);
   }
 
+  public void collectIndexedItems(IndexedItemCollection indexedItems) {
+    getReference().collectIndexedItems(indexedItems);
+    DexEncodedField definition = getDefinition();
+    definition.annotations().collectIndexedItems(indexedItems);
+    if (definition.isStatic() && definition.hasExplicitStaticValue()) {
+      definition.getStaticValue().collectIndexedItems(indexedItems);
+    }
+  }
+
   public boolean isStructurallyEqualTo(ProgramField other) {
     return getDefinition() == other.getDefinition() && getHolder() == other.getHolder();
   }
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 e69ade7..a1fe5c7 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -3,9 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
@@ -35,6 +37,20 @@
         context, this, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
   }
 
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, GraphLens graphLens, LensCodeRewriterUtils rewriter) {
+    DexEncodedMethod definition = getDefinition();
+    assert !definition.isObsolete();
+    assert !definition.hasCode() || definition.getCode().isDexCode();
+    getReference().collectIndexedItems(indexedItems);
+    Code code = definition.getCode();
+    if (code != null && code.isDexCode()) {
+      code.asDexCode().collectIndexedItems(indexedItems, this, graphLens, rewriter);
+    }
+    definition.annotations().collectIndexedItems(indexedItems);
+    definition.parameterAnnotationsList.collectIndexedItems(indexedItems);
+  }
+
   public boolean isStructurallyEqualTo(ProgramMethod other) {
     return getDefinition() == other.getDefinition() && getHolder() == other.getHolder();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 3316b64..82b02c4 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -177,6 +177,12 @@
       return resolvedMethod;
     }
 
+    public ProgramMethod getResolvedProgramMethod() {
+      return resolvedHolder.isProgramClass()
+          ? new ProgramMethod(resolvedHolder.asProgramClass(), resolvedMethod)
+          : null;
+    }
+
     public DexClassAndMethod getResolutionPair() {
       return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 51ef31e..1f751ad 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -17,13 +17,16 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
@@ -31,8 +34,10 @@
  * class {@link ClassMerger#target}. While performing merging, this class tracks which methods have
  * been moved, as well as which fields have been remapped in the {@link ClassMerger#lensBuilder}.
  */
-class ClassMerger {
-  private final AppView<?> appView;
+public class ClassMerger {
+  public static final String CLASS_ID_FIELD_NAME = "$r8$classId";
+
+  private final AppView<AppInfoWithLiveness> appView;
   private final DexProgramClass target;
   private final Collection<DexProgramClass> toMergeGroup;
   private final DexItemFactory dexItemFactory;
@@ -40,41 +45,33 @@
   private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
 
   private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
-  private final Map<DexProto, ConstructorMerger.Builder> constructorMergers;
+  private final Collection<VirtualMethodMerger> virtualMethodMergers;
+  private final Collection<ConstructorMerger> constructorMergers;
   private final DexField classIdField;
 
-  ClassMerger(
-      AppView<?> appView,
+  private ClassMerger(
+      AppView<AppInfoWithLiveness> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       DexProgramClass target,
-      Collection<DexProgramClass> toMergeGroup) {
+      Collection<DexProgramClass> toMergeGroup,
+      DexField classIdField,
+      Collection<VirtualMethodMerger> virtualMethodMergers,
+      Collection<ConstructorMerger> constructorMergers) {
     this.appView = appView;
     this.lensBuilder = lensBuilder;
     this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
     this.target = target;
     this.toMergeGroup = toMergeGroup;
+    this.classIdField = classIdField;
+    this.virtualMethodMergers = virtualMethodMergers;
+    this.constructorMergers = constructorMergers;
 
-    this.constructorMergers = new IdentityHashMap<>();
     this.dexItemFactory = appView.dexItemFactory();
 
-    // TODO(b/165498187): ensure the name for the field is fresh
-    classIdField = dexItemFactory.createField(target.type, dexItemFactory.intType, "$r8$classId");
-
     buildClassIdentifierMap();
   }
 
-  Wrapper<DexMethod> bySignature(DexMethod method) {
-    return MethodSignatureEquivalence.get().wrap(method);
-  }
-
-  void addConstructor(DexEncodedMethod method) {
-    assert method.isInstanceInitializer();
-    constructorMergers
-        .computeIfAbsent(method.proto(), ignore -> new ConstructorMerger.Builder())
-        .add(method);
-  }
-
   void buildClassIdentifierMap() {
     classIdentifiers.put(target.type, 0);
     for (DexProgramClass toMerge : toMergeGroup) {
@@ -83,21 +80,16 @@
   }
 
   void merge(DexProgramClass toMerge) {
-    toMerge.forEachProgramMethod(
-        programMethod -> {
-          DexEncodedMethod method = programMethod.getDefinition();
-          assert !method.isClassInitializer();
+    toMerge.forEachProgramDirectMethod(
+        method -> {
+          DexEncodedMethod definition = method.getDefinition();
+          assert !definition.isClassInitializer();
 
-          if (method.isInstanceInitializer()) {
-            addConstructor(method);
-          } else {
-            // TODO(b/166427795): Ensure that overriding relationships are not changed.
-            assert method.isVirtualMethod();
-
-            DexMethod newMethod = renameMethod(programMethod);
-            // TODO(b/165000217): Add all methods to `target` in one go using addVirtualMethods().;
-            target.addVirtualMethod(method.toTypeSubstitutedMethod(newMethod));
-            lensBuilder.moveMethod(method.method, newMethod);
+          if (!definition.isInstanceInitializer()) {
+            DexMethod newMethod = renameMethod(method);
+            // TODO(b/165000217): Add all methods to `target` in one go using addDirectMethods().
+            target.addDirectMethod(definition.toTypeSubstitutedMethod(newMethod));
+            lensBuilder.moveMethod(definition.getReference(), newMethod);
           }
         });
 
@@ -123,24 +115,15 @@
   }
 
   void mergeConstructors() {
-    for (ConstructorMerger.Builder builder : constructorMergers.values()) {
-      ConstructorMerger constructorMerger = builder.build(appView, target, classIdField);
-      constructorMerger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
+    for (ConstructorMerger merger : constructorMergers) {
+      merger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
     }
   }
 
-  /**
-   * To ensure constructor merging happens correctly, add all of the target constructors methods to
-   * constructor mergers.
-   */
-  void addTargetConstructors() {
-    target.forEachProgramDirectMethod(
-        programMethod -> {
-          DexEncodedMethod method = programMethod.getDefinition();
-          if (method.isInstanceInitializer()) {
-            addConstructor(method);
-          }
-        });
+  void mergeVirtualMethods() {
+    for (VirtualMethodMerger merger : virtualMethodMergers) {
+      merger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
+    }
   }
 
   void appendClassIdField() {
@@ -155,13 +138,102 @@
   }
 
   public void mergeGroup() {
-    addTargetConstructors();
     appendClassIdField();
 
     for (DexProgramClass clazz : toMergeGroup) {
       merge(clazz);
+      lensBuilder.mapType(clazz.type, target.type);
     }
 
     mergeConstructors();
+    mergeVirtualMethods();
+  }
+
+  public static class Builder {
+    private final DexProgramClass target;
+    private final Collection<DexProgramClass> toMergeGroup = new ArrayList<>();
+    private final Map<DexProto, ConstructorMerger.Builder> constructorMergerBuilders =
+        new IdentityHashMap<>();
+    private final Map<Wrapper<DexMethod>, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
+        new LinkedHashMap<>();
+
+    public Builder(DexProgramClass target) {
+      this.target = target;
+      setupForMethodMerging(target);
+    }
+
+    public Builder mergeClass(DexProgramClass toMerge) {
+      setupForMethodMerging(toMerge);
+      toMergeGroup.add(toMerge);
+      return this;
+    }
+
+    public Builder addClassesToMerge(Collection<DexProgramClass> toMerge) {
+      toMerge.forEach(this::mergeClass);
+      return this;
+    }
+
+    void setupForMethodMerging(DexProgramClass toMerge) {
+      toMerge.forEachProgramDirectMethod(
+          method -> {
+            DexEncodedMethod definition = method.getDefinition();
+            assert !definition.isClassInitializer();
+
+            if (definition.isInstanceInitializer()) {
+              addConstructor(method);
+            }
+          });
+
+      toMerge.forEachProgramVirtualMethod(this::addVirtualMethod);
+    }
+
+    void addConstructor(ProgramMethod method) {
+      assert method.getDefinition().isInstanceInitializer();
+      constructorMergerBuilders
+          .computeIfAbsent(
+              method.getDefinition().getProto(), ignore -> new ConstructorMerger.Builder())
+          .add(method.getDefinition());
+    }
+
+    void addVirtualMethod(ProgramMethod method) {
+      assert method.getDefinition().isNonPrivateVirtualMethod();
+      virtualMethodMergerBuilders
+          .computeIfAbsent(
+              MethodSignatureEquivalence.get().wrap(method.getReference()),
+              ignore -> new VirtualMethodMerger.Builder())
+          .add(method);
+    }
+
+    public ClassMerger build(
+        AppView<AppInfoWithLiveness> appView,
+        HorizontalClassMergerGraphLens.Builder lensBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      // TODO(b/165498187): ensure the name for the field is fresh
+      DexField classIdField =
+          dexItemFactory.createField(target.type, dexItemFactory.intType, CLASS_ID_FIELD_NAME);
+
+      Collection<VirtualMethodMerger> virtualMethodMergers =
+          new ArrayList<>(virtualMethodMergerBuilders.size());
+      for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
+        virtualMethodMergers.add(builder.build(appView, target, classIdField));
+      }
+
+      Collection<ConstructorMerger> constructorMergers =
+          new ArrayList<>(constructorMergerBuilders.size());
+      for (ConstructorMerger.Builder builder : constructorMergerBuilders.values()) {
+        constructorMergers.add(builder.build(appView, target, classIdField));
+      }
+
+      return new ClassMerger(
+          appView,
+          lensBuilder,
+          fieldAccessChangesBuilder,
+          target,
+          toMergeGroup,
+          classIdField,
+          virtualMethodMergers,
+          constructorMergers);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 76f4814..cd2d31c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -107,7 +107,8 @@
 
   private MethodAccessFlags getAccessFlags() {
     // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
-    return MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, true);
+    return MethodAccessFlags.fromSharedAccessFlags(
+        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
   }
 
   /** Synthesize a new method which selects the constructor based on a parameter type. */
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index f7ac858..a938abb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.policies.NoFields;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInternalUtilityClasses;
@@ -17,12 +16,11 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -32,7 +30,7 @@
 
   public HorizontalClassMerger(
       AppView<AppInfoWithLiveness> appView,
-      MainDexClasses mainDexClasses,
+      MainDexTracingResult mainDexClasses,
       ClassMergingEnqueuerExtension classMergingEnqueuerExtension) {
     this.appView = appView;
 
@@ -52,6 +50,7 @@
     this.policyExecutor = new SimplePolicyExecutor(policies);
   }
 
+  // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
   public HorizontalClassMergerGraphLens run() {
     Map<FieldMultiset, Collection<DexProgramClass>> classes = new HashMap<>();
 
@@ -63,23 +62,32 @@
     // Run the policies on all collected classes to produce a final grouping.
     Collection<Collection<DexProgramClass>> groups = policyExecutor.run(classes.values());
 
-    return createLens(groups);
-  }
-
-  // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
-  /**
-   * Merges all class groups using {@link ClassMerger}. Then fix all references to merged classes
-   * using the {@link TreeFixer}. Constructs a graph lens containing all changes while performing
-   * merging.
-   */
-  private HorizontalClassMergerGraphLens createLens(
-      Collection<Collection<DexProgramClass>> groups) {
-    Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
     HorizontalClassMergerGraphLens.Builder lensBuilder =
         new HorizontalClassMergerGraphLens.Builder();
     FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder =
         new FieldAccessInfoCollectionModifier.Builder();
 
+    // Set up a class merger for each group.
+    Collection<ClassMerger> classMergers =
+        initializeClassMergers(lensBuilder, fieldAccessChangesBuilder, groups);
+
+    // Merge the classes.
+    applyClassMergers(classMergers);
+
+    // Generate the class lens.
+    return createLens(lensBuilder, fieldAccessChangesBuilder);
+  }
+
+  /**
+   * Prepare horizontal class merging by determining which virtual methods and constructors need to
+   * be merged and how the merging should be performed.
+   */
+  private Collection<ClassMerger> initializeClassMergers(
+      HorizontalClassMergerGraphLens.Builder lensBuilder,
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
+      Collection<Collection<DexProgramClass>> groups) {
+    Collection<ClassMerger> classMergers = new ArrayList<>();
+
     // TODO(b/166577694): Replace Collection<DexProgramClass> with MergeGroup
     for (Collection<DexProgramClass> group : groups) {
       assert !group.isEmpty();
@@ -87,18 +95,33 @@
       DexProgramClass target = group.stream().findFirst().get();
       group.remove(target);
 
-      for (DexProgramClass clazz : group) {
-        mergedClasses.put(clazz.type, target.type);
-      }
-
       ClassMerger merger =
-          new ClassMerger(appView, lensBuilder, fieldAccessChangesBuilder, target, group);
-      merger.mergeGroup();
+          new ClassMerger.Builder(target)
+              .addClassesToMerge(group)
+              .build(appView, lensBuilder, fieldAccessChangesBuilder);
+      classMergers.add(merger);
     }
 
+    return classMergers;
+  }
+
+  /** Merges all class groups using {@link ClassMerger}. */
+  private void applyClassMergers(Collection<ClassMerger> classMergers) {
+    for (ClassMerger merger : classMergers) {
+      merger.mergeGroup();
+    }
+  }
+
+  /**
+   * Fix all references to merged classes using the {@link TreeFixer}. Construct a graph lens
+   * containing all changes performed by horizontal class merging.
+   */
+  private HorizontalClassMergerGraphLens createLens(
+      HorizontalClassMergerGraphLens.Builder lensBuilder,
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
+
     HorizontalClassMergerGraphLens lens =
-        new TreeFixer(appView, lensBuilder, fieldAccessChangesBuilder, mergedClasses)
-            .fixupTypeReferences();
+        new TreeFixer(appView, lensBuilder, fieldAccessChangesBuilder).fixupTypeReferences();
     return lens;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 4389282..482ade5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -83,10 +83,10 @@
   }
 
   public static class Builder {
-    protected final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
-    protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
-    protected final Map<DexMethod, Set<DexMethod>> completeInverseMethodMap =
-        new IdentityHashMap<>();
+    private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
+    private final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
+    private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+    private final Map<DexMethod, Set<DexMethod>> completeInverseMethodMap = new IdentityHashMap<>();
 
     private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
     private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures = new IdentityHashMap<>();
@@ -95,16 +95,15 @@
 
     Builder() {}
 
-    public HorizontalClassMergerGraphLens build(
-        AppView<?> appView, Map<DexType, DexType> mergedClasses) {
-      if (mergedClasses.isEmpty()) {
+    public HorizontalClassMergerGraphLens build(AppView<?> appView) {
+      if (typeMap.isEmpty()) {
         return null;
       } else {
         BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
         return new HorizontalClassMergerGraphLens(
             appView,
             constructorIds,
-            mergedClasses,
+            typeMap,
             fieldMap,
             methodMap,
             originalFieldSignatures,
@@ -114,6 +113,15 @@
       }
     }
 
+    public DexType lookupType(DexType type) {
+      return typeMap.getOrDefault(type, type);
+    }
+
+    public Builder mapType(DexType from, DexType to) {
+      typeMap.put(from, to);
+      return this;
+    }
+
     /** Bidirectional mapping from one method to another. */
     public Builder moveMethod(DexMethod from, DexMethod to) {
       mapMethod(from, to);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index 261e8d2..1ce2ee4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -10,6 +10,13 @@
 public abstract class MultiClassPolicy extends Policy {
 
   /**
+   * Remove all groups containing no or only a single class, as there is no point in merging these.
+   */
+  protected void removeTrivialGroups(Collection<Collection<DexProgramClass>> groups) {
+    groups.removeIf(group -> group.size() < 2);
+  }
+
+  /**
    * Apply the multi class policy to a group of program classes.
    *
    * @param group This is a group of program classes which can currently still be merged.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
index 3e6254c..9ec4e5f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -32,7 +32,7 @@
           j.remove();
         }
       }
-      if (group.isEmpty()) {
+      if (group.size() < 2) {
         i.remove();
       }
     }
@@ -56,6 +56,9 @@
       } else if (policy instanceof MultiClassPolicy) {
         groups = applyMultiClassPolicy((MultiClassPolicy) policy, groups);
       }
+
+      // Any policy should not return any trivial groups.
+      assert groups.stream().allMatch(group -> group.size() >= 2);
     }
 
     return groups;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 7d0d5c0..087c6ef 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -23,12 +23,10 @@
 
 /**
  * The tree fixer traverses all program classes and finds and fixes references to old classes which
- * have been remapped to new classes by the class merger (stored in {@link
- * TreeFixer#mergedClasses}). While doing so, all updated changes are tracked in {@link
- * TreeFixer#lensBuilder}.
+ * have been remapped to new classes by the class merger. While doing so, all updated changes are
+ * tracked in {@link TreeFixer#lensBuilder}.
  */
 class TreeFixer {
-  private final Map<DexType, DexType> mergedClasses;
   private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
   private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
@@ -37,9 +35,7 @@
   public TreeFixer(
       AppView<AppInfoWithLiveness> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
-      Map<DexType, DexType> mergedClasses) {
-    this.mergedClasses = mergedClasses;
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
     this.lensBuilder = lensBuilder;
     this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
     this.appView = appView;
@@ -48,11 +44,12 @@
   HorizontalClassMergerGraphLens fixupTypeReferences() {
     // Globally substitute merged class types in protos and holders.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.superType = lensBuilder.lookupType(clazz.superType);
       clazz.getMethodCollection().replaceMethods(this::fixupMethod);
       fixupFields(clazz.staticFields(), clazz::setStaticField);
       fixupFields(clazz.instanceFields(), clazz::setInstanceField);
     }
-    HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
+    HorizontalClassMergerGraphLens lens = lensBuilder.build(appView);
 
     fieldAccessChangesBuilder.build(this::fixupMethod).modify(appView);
 
@@ -133,8 +130,10 @@
       return type.replaceBaseType(fixed, appView.dexItemFactory());
     }
     if (type.isClassType()) {
-      while (mergedClasses.containsKey(type)) {
-        type = mergedClasses.get(type);
+      while (true) {
+        DexType mapped = lensBuilder.lookupType(type);
+        if (mapped == type) break;
+        type = mapped;
       }
     }
     return type;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPoint.java
new file mode 100644
index 0000000..494e383
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPoint.java
@@ -0,0 +1,163 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Invoke.Type;
+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 com.android.tools.r8.utils.IntBox;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Assuming a method signature <code>
+ *   void method([args]);
+ * </code>. This class generates code depending on which of the following cases it matches.
+ *
+ * <p>If the method does not override a method and is implemented by many (e.g. 2) classes:
+ *
+ * <pre>
+ *   void method([args]) {
+ *     switch (classId) {
+ *       case 0:
+ *         return method$1([args]);
+ *       default:
+ *         return method$2([args]);
+ *     }
+ *   }
+ * </pre>
+ *
+ * <p>If the method overrides a method and is implemented by any number of classes:
+ *
+ * <pre>
+ *   void method([args]) {
+ *     switch (classId) {
+ *       case 0:
+ *         return method$1([args]);
+ *       // ... further cases ...
+ *       default:
+ *         return super.method$1([args]);
+ *     }
+ *   }
+ * </pre>
+ */
+public class VirtualMethodEntryPoint extends SyntheticSourceCode {
+  private final Int2ReferenceSortedMap<DexMethod> mappedMethods;
+  private final DexField classIdField;
+  private final DexMethod superMethod;
+
+  public VirtualMethodEntryPoint(
+      Int2ReferenceSortedMap<DexMethod> mappedMethods,
+      DexField classIdField,
+      DexMethod superMethod,
+      DexMethod newMethod,
+      Position callerPosition,
+      DexMethod originalMethod) {
+    super(newMethod.holder, newMethod, callerPosition, originalMethod);
+
+    assert classIdField != null;
+
+    this.mappedMethods = mappedMethods;
+    this.classIdField = classIdField;
+    this.superMethod = superMethod;
+  }
+
+  void addInvokeDirect(DexMethod method) {
+    add(
+        builder -> {
+          List<Value> arguments = new ArrayList<>(method.getArity() + 1);
+          arguments.add(builder.getReceiverValue());
+          if (builder.getArgumentValues() != null) {
+            arguments.addAll(builder.getArgumentValues());
+          }
+          builder.addInvoke(Type.DIRECT, method, method.proto, arguments, false);
+        });
+  }
+
+  void addInvokeSuper() {
+    assert superMethod != null;
+
+    add(
+        builder -> {
+          List<Value> arguments = new ArrayList<>(method.getArity() + 1);
+          arguments.add(builder.getReceiverValue());
+          if (builder.getArgumentValues() != null) {
+            arguments.addAll(builder.getArgumentValues());
+          }
+          builder.addInvoke(Type.SUPER, superMethod, superMethod.proto, arguments, false);
+        });
+  }
+
+  void handleReturn(int retRegister) {
+    if (proto.returnType.isVoidType()) {
+      add(IRBuilder::addReturn, endsBlock);
+    } else {
+      add(builder -> builder.addMoveResult(retRegister));
+      add(builder -> builder.addReturn(retRegister), endsBlock);
+    }
+  }
+
+  @Override
+  protected void prepareInstructions() {
+    int casesCount = mappedMethods.size();
+
+    // If there is no super method, use one of the cases as a fallthrough case.
+    if (superMethod == null) {
+      casesCount--;
+    }
+
+    assert casesCount > 0;
+
+    // Return value register if needed.
+    int returnRegister =
+        !proto.returnType.isVoidType() ? nextRegister(ValueType.fromDexType(proto.returnType)) : -1;
+
+    int[] keys = new int[casesCount];
+    int[] offsets = new int[casesCount];
+    IntBox fallthrough = new IntBox();
+
+    // Fetch the class id from the class id field.
+    int idRegister = nextRegister(ValueType.INT);
+    add(builder -> builder.addInstanceGet(idRegister, getReceiverRegister(), classIdField));
+
+    int switchIndex = lastInstructionIndex();
+    add(
+        builder -> builder.addSwitch(idRegister, keys, fallthrough.get(), offsets),
+        builder -> endsSwitch(builder, switchIndex, fallthrough.get(), offsets));
+
+    int index = 0;
+    for (Entry<DexMethod> entry : mappedMethods.int2ReferenceEntrySet()) {
+      int classId = entry.getIntKey();
+      DexMethod mappedMethod = entry.getValue();
+
+      // If there is no super method, then use the last case as the default case.
+      if (index >= casesCount) {
+        fallthrough.set(nextInstructionIndex());
+      } else {
+        keys[index] = classId;
+        offsets[index] = nextInstructionIndex();
+      }
+
+      addInvokeDirect(mappedMethod);
+      handleReturn(returnRegister);
+
+      index++;
+    }
+
+    // If the super class implements this method, then the fallthrough case should execute it.
+    if (superMethod != null) {
+      fallthrough.set(nextInstructionIndex());
+      addInvokeSuper();
+      handleReturn(returnRegister);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPointSynthesizedCode.java
new file mode 100644
index 0000000..d6690a9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPointSynthesizedCode.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.function.Consumer;
+
+public class VirtualMethodEntryPointSynthesizedCode extends SynthesizedCode {
+  private final Int2ReferenceSortedMap<DexMethod> mappedMethods;
+  private final DexField classIdField;
+
+  public VirtualMethodEntryPointSynthesizedCode(
+      Int2ReferenceSortedMap<DexMethod> mappedMethods,
+      DexField classIdField,
+      DexMethod superMethod,
+      DexMethod method,
+      DexMethod originalMethod) {
+    super(
+        position ->
+            new VirtualMethodEntryPoint(
+                mappedMethods, classIdField, superMethod, method, position, originalMethod));
+
+    this.mappedMethods = mappedMethods;
+    this.classIdField = classIdField;
+  }
+
+  @Override
+  public Consumer<UseRegistry> getRegistryCallback() {
+    return this::registerReachableDefinitions;
+  }
+
+  private void registerReachableDefinitions(UseRegistry registry) {
+    for (DexMethod mappedMethod : mappedMethods.values()) {
+      registry.registerInvokeDirect(mappedMethod);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
new file mode 100644
index 0000000..5e20c04f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.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.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class VirtualMethodMerger {
+  private final DexProgramClass target;
+  private final DexItemFactory dexItemFactory;
+  private final Collection<ProgramMethod> methods;
+  private final DexField classIdField;
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexMethod superMethod;
+
+  public VirtualMethodMerger(
+      AppView<AppInfoWithLiveness> appView,
+      DexProgramClass target,
+      Collection<ProgramMethod> methods,
+      DexField classIdField,
+      DexMethod superMethod) {
+    this.dexItemFactory = appView.dexItemFactory();
+    this.target = target;
+    this.classIdField = classIdField;
+    this.methods = methods;
+    this.appView = appView;
+    this.superMethod = superMethod;
+  }
+
+  public static class Builder {
+    private final Collection<ProgramMethod> methods = new ArrayList<>();
+
+    public Builder add(ProgramMethod constructor) {
+      methods.add(constructor);
+      return this;
+    }
+
+    /** Get the super method handle if this method overrides a parent method. */
+    private DexMethod superMethod(AppView<AppInfoWithLiveness> appView, DexProgramClass target) {
+      // TODO(b/167981556): Correctly detect super methods defined on interfaces.
+      DexMethod template = methods.iterator().next().getReference();
+      SingleResolutionResult resolutionResult =
+          appView
+              .withLiveness()
+              .appInfo()
+              .resolveMethodOnClass(template, target.superType)
+              .asSingleResolution();
+      if (resolutionResult == null) {
+        return null;
+      }
+      return resolutionResult.getResolvedMethod().method;
+    }
+
+    public VirtualMethodMerger build(
+        AppView<AppInfoWithLiveness> appView, DexProgramClass target, DexField classIdField) {
+      DexMethod superMethod = superMethod(appView, target);
+      return new VirtualMethodMerger(appView, target, methods, classIdField, superMethod);
+    }
+  }
+
+  private DexMethod moveMethod(ProgramMethod oldMethod) {
+    DexMethod oldMethodReference = oldMethod.getReference();
+    DexMethod method =
+        dexItemFactory.createFreshMethodName(
+            oldMethodReference.name.toSourceString(),
+            oldMethod.getHolderType(),
+            oldMethodReference.proto,
+            target.type,
+            tryMethod -> target.lookupMethod(tryMethod) == null);
+
+    if (oldMethod.getHolderType() == target.type) {
+      target.removeMethod(oldMethod.getReference());
+    }
+
+    DexEncodedMethod encodedMethod = oldMethod.getDefinition().toTypeSubstitutedMethod(method);
+    MethodAccessFlags flags = encodedMethod.accessFlags;
+    flags.unsetProtected();
+    flags.unsetPublic();
+    flags.setPrivate();
+    target.addDirectMethod(encodedMethod);
+
+    return encodedMethod.method;
+  }
+
+  private MethodAccessFlags getAccessFlags() {
+    // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
+    return methods.iterator().next().getDefinition().getAccessFlags();
+  }
+
+
+  /**
+   * If there is only a single method that does not override anything then it is safe to just move
+   * it to the target type if it is not already in it.
+   */
+  public void mergeTrivial(HorizontalClassMergerGraphLens.Builder lensBuilder) {
+    ProgramMethod method = methods.iterator().next();
+
+    if (method.getHolderType() != target.type) {
+      // If the method is not in the target type, move it and record it in the lens.
+      DexEncodedMethod newMethod =
+          method.getDefinition().toRenamedHolderMethod(target.type, dexItemFactory);
+      target.addVirtualMethod(newMethod);
+      lensBuilder.moveMethod(method.getReference(), newMethod.getReference());
+    }
+  }
+
+  public void merge(
+      HorizontalClassMergerGraphLens.Builder lensBuilder,
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
+      Reference2IntMap classIdentifiers) {
+
+    assert !methods.isEmpty();
+
+    // Handle trivial merges.
+    if (superMethod == null && methods.size() == 1) {
+      mergeTrivial(lensBuilder);
+      return;
+    }
+
+    Int2ReferenceSortedMap<DexMethod> classIdToMethodMap = new Int2ReferenceAVLTreeMap<>();
+
+    int classFileVersion = -1;
+    for (ProgramMethod method : methods) {
+      if (method.getDefinition().hasClassFileVersion()) {
+        classFileVersion =
+            Integer.max(classFileVersion, method.getDefinition().getClassFileVersion());
+      }
+      DexMethod newMethod = moveMethod(method);
+      lensBuilder.recordOriginalSignature(method.getReference(), newMethod);
+      classIdToMethodMap.put(classIdentifiers.getInt(method.getHolderType()), newMethod);
+    }
+
+    // Use the first of the original methods as the original method for the merged constructor.
+    DexMethod originalMethodReference = methods.iterator().next().getReference();
+
+    DexMethod newMethodReference =
+        dexItemFactory.createMethod(
+            target.type, originalMethodReference.proto, originalMethodReference.name);
+    AbstractSynthesizedCode synthesizedCode =
+        new VirtualMethodEntryPointSynthesizedCode(
+            classIdToMethodMap,
+            classIdField,
+            superMethod,
+            newMethodReference,
+            originalMethodReference);
+    DexEncodedMethod newMethod =
+        new DexEncodedMethod(
+            newMethodReference,
+            getAccessFlags(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            synthesizedCode,
+            classFileVersion,
+            true);
+
+    // Map each old method to the newly synthesized method in the graph lens.
+    for (ProgramMethod oldMethod : methods) {
+      lensBuilder.mapMethod(oldMethod.getReference(), newMethodReference);
+    }
+    lensBuilder.recordExtraOriginalSignature(originalMethodReference, newMethodReference);
+
+    target.addVirtualMethod(newMethod);
+
+    fieldAccessChangesBuilder.fieldReadByMethod(classIdField, newMethod.method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
index 4e1987e..5b274d1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
@@ -22,6 +22,7 @@
           .computeIfAbsent(clazz.superType, ignore -> new LinkedList<DexProgramClass>())
           .add(clazz);
     }
+    removeTrivialGroups(groups.values());
     return groups.values();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index c2ffda0..41a0192 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -12,14 +12,30 @@
 
   public abstract boolean isNonTrivial();
 
+  public boolean isSingleBoolean() {
+    return false;
+  }
+
   public boolean isBottom() {
     return false;
   }
 
-  public boolean isZero() {
+  public boolean isFalse() {
     return false;
   }
 
+  public boolean isTrue() {
+    return false;
+  }
+
+  public final boolean isNull() {
+    return isFalse();
+  }
+
+  public final boolean isZero() {
+    return isFalse();
+  }
+
   /**
    * Returns true if this abstract value represents a single concrete value (i.e., the
    * concretization of this abstract value has size 1).
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 98a7fa4..76e6ac8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -27,11 +27,21 @@
   }
 
   @Override
-  public boolean isZero() {
+  public boolean isSingleBoolean() {
+    return isFalse() || isTrue();
+  }
+
+  @Override
+  public boolean isFalse() {
     return value == 0;
   }
 
   @Override
+  public boolean isTrue() {
+    return value == 1;
+  }
+
+  @Override
   public boolean isSingleNumberValue() {
     return true;
   }
@@ -46,14 +56,26 @@
     return value != 0;
   }
 
-  public long getValue() {
-    return value;
+  public double getDoubleValue() {
+    return Double.longBitsToDouble(value);
+  }
+
+  public float getFloatValue() {
+    return Float.intBitsToFloat((int) value);
   }
 
   public int getIntValue() {
     return (int) value;
   }
 
+  public long getLongValue() {
+    return value;
+  }
+
+  public long getValue() {
+    return value;
+  }
+
   @Override
   public boolean equals(Object o) {
     return this == o;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 3d7ae52..23808f4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -121,4 +121,10 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder(super.toString());
+    return builder.append("; ").append(type.toSourceString()).toString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 1c19488..0e40a2a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -3,6 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.code.InvokeCustomRange;
+import com.android.tools.r8.code.InvokeDirectRange;
+import com.android.tools.r8.code.InvokeInterfaceRange;
+import com.android.tools.r8.code.InvokePolymorphicRange;
+import com.android.tools.r8.code.InvokeStaticRange;
+import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.code.InvokeVirtualRange;
 import com.android.tools.r8.code.MoveResult;
 import com.android.tools.r8.code.MoveResultObject;
 import com.android.tools.r8.code.MoveResultWide;
@@ -20,19 +27,60 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.List;
 import java.util.Set;
+import org.objectweb.asm.Opcodes;
 
 public abstract class Invoke extends Instruction {
 
+  private static final int NO_SUCH_DEX_INSTRUCTION = -1;
+
   public enum Type {
-    DIRECT,
-    INTERFACE,
-    STATIC,
-    SUPER,
-    VIRTUAL,
-    NEW_ARRAY,
-    MULTI_NEW_ARRAY,
-    CUSTOM,
-    POLYMORPHIC;
+    DIRECT(com.android.tools.r8.code.InvokeDirect.OPCODE, InvokeDirectRange.OPCODE),
+    INTERFACE(com.android.tools.r8.code.InvokeInterface.OPCODE, InvokeInterfaceRange.OPCODE),
+    STATIC(com.android.tools.r8.code.InvokeStatic.OPCODE, InvokeStaticRange.OPCODE),
+    SUPER(com.android.tools.r8.code.InvokeSuper.OPCODE, InvokeSuperRange.OPCODE),
+    VIRTUAL(com.android.tools.r8.code.InvokeVirtual.OPCODE, InvokeVirtualRange.OPCODE),
+    NEW_ARRAY(com.android.tools.r8.code.NewArray.OPCODE, NO_SUCH_DEX_INSTRUCTION),
+    MULTI_NEW_ARRAY(NO_SUCH_DEX_INSTRUCTION, NO_SUCH_DEX_INSTRUCTION),
+    CUSTOM(com.android.tools.r8.code.InvokeCustom.OPCODE, InvokeCustomRange.OPCODE),
+    POLYMORPHIC(com.android.tools.r8.code.InvokePolymorphic.OPCODE, InvokePolymorphicRange.OPCODE);
+
+    private final int dexOpcode;
+    private final int dexOpcodeRange;
+
+    Type(int dexOpcode, int dexOpcodeRange) {
+      this.dexOpcode = dexOpcode;
+      this.dexOpcodeRange = dexOpcodeRange;
+    }
+
+    public int getCfOpcode() {
+      switch (this) {
+        case DIRECT:
+          return Opcodes.INVOKESPECIAL;
+        case INTERFACE:
+          return Opcodes.INVOKEINTERFACE;
+        case STATIC:
+          return Opcodes.INVOKESTATIC;
+        case SUPER:
+          return Opcodes.INVOKESPECIAL;
+        case VIRTUAL:
+          return Opcodes.INVOKEVIRTUAL;
+        case NEW_ARRAY:
+        case MULTI_NEW_ARRAY:
+        case POLYMORPHIC:
+        default:
+          throw new Unreachable();
+      }
+    }
+
+    public int getDexOpcode() {
+      assert dexOpcode >= 0;
+      return dexOpcode;
+    }
+
+    public int getDexOpcodeRange() {
+      assert dexOpcodeRange >= 0;
+      return dexOpcodeRange;
+    }
 
     public MethodHandleType toMethodHandle(DexMethod targetMethod) {
       switch (this) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 52ab07a..f5a51c8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -273,7 +273,7 @@
       }
 
       FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.field);
-      if (fieldAccessInfo != null) {
+      if (fieldAccessInfo != null && fieldAccessInfo.hasKnownWriteContexts()) {
         if (fieldAccessInfo.getNumberOfWriteContexts() == 1) {
           fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge);
         }
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 4c10624..5107d27 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import static com.android.tools.r8.graph.DexAnnotation.readAnnotationSynthesizedClassMap;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
@@ -90,6 +91,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -190,7 +192,7 @@
    * (i.e., whether we are running R8). See {@link AppView#enableWholeProgramOptimizations()}.
    */
   public IRConverter(
-      AppView<?> appView, Timing timing, CfgPrinter printer, MainDexClasses mainDexClasses) {
+      AppView<?> appView, Timing timing, CfgPrinter printer, MainDexTracingResult mainDexClasses) {
     assert appView.appInfo().hasLiveness() || appView.graphLens().isIdentityLens();
     assert appView.options() != null;
     assert appView.options().programConsumer != null;
@@ -309,7 +311,7 @@
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
       this.methodOptimizationInfoCollector =
-          new MethodOptimizationInfoCollector(appViewWithLiveness);
+          new MethodOptimizationInfoCollector(appViewWithLiveness, this);
       if (options.isMinifying()) {
         this.identifierNameStringMarker = new IdentifierNameStringMarker(appViewWithLiveness);
       } else {
@@ -364,16 +366,16 @@
 
   /** Create an IR converter for processing methods with full program optimization disabled. */
   public IRConverter(AppView<?> appView, Timing timing) {
-    this(appView, timing, null, MainDexClasses.NONE);
+    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, MainDexClasses.NONE);
+    this(appView, timing, printer, MainDexTracingResult.NONE);
   }
 
   public IRConverter(AppInfo appInfo, Timing timing, CfgPrinter printer) {
-    this(AppView.createForD8(appInfo), timing, printer, MainDexClasses.NONE);
+    this(AppView.createForD8(appInfo), timing, printer, MainDexTracingResult.NONE);
   }
 
   private boolean enableTwrCloseResourceDesugaring() {
@@ -493,42 +495,54 @@
     processCovariantReturnTypeAnnotations(builder);
     generateDesugaredLibraryAPIWrappers(builder, executor);
 
-    handleSynthesizedClassMapping(builder);
+    handleSynthesizedClassMapping(appView, builder);
     timing.end();
 
     DexApplication app = builder.build();
-    appView.setAppInfo(new AppInfo(app, appView.appInfo().getSyntheticItems().commit(app)));
+    appView.setAppInfo(
+        new AppInfo(
+            app,
+            appView.appInfo().getMainDexClasses(),
+            appView.appInfo().getSyntheticItems().commit(app)));
   }
 
-  private void handleSynthesizedClassMapping(Builder<?> builder) {
+  private void handleSynthesizedClassMapping(AppView<?> appView, Builder<?> builder) {
     if (options.intermediate) {
       updateSynthesizedClassMapping(builder);
     }
 
-    updateMainDexListWithSynthesizedClassMap(builder);
+    updateMainDexListWithSynthesizedClassMap(appView, builder);
 
     if (!options.intermediate) {
       clearSynthesizedClassMapping(builder);
     }
   }
 
-  private void updateMainDexListWithSynthesizedClassMap(Builder<?> builder) {
-    Set<DexType> inputMainDexList = builder.getMainDexList();
-    if (!inputMainDexList.isEmpty()) {
-      Map<DexType, DexProgramClass> programClasses = builder.getProgramClasses().stream()
-          .collect(Collectors.toMap(
-              programClass -> programClass.type,
-              Function.identity()));
-      Collection<DexType> synthesized = new ArrayList<>();
-      for (DexType dexType : inputMainDexList) {
-        DexProgramClass programClass = programClasses.get(dexType);
-        if (programClass != null) {
-          synthesized.addAll(DexAnnotation.readAnnotationSynthesizedClassMap(
-              programClass, builder.dexItemFactory));
-        }
-      }
-      builder.addToMainDexList(synthesized);
+  private void updateMainDexListWithSynthesizedClassMap(AppView<?> appView, Builder<?> builder) {
+    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
+    if (mainDexClasses.isEmpty()) {
+      return;
     }
+    Map<DexType, DexProgramClass> programClasses =
+        builder.getProgramClasses().stream()
+            .collect(Collectors.toMap(programClass -> programClass.type, Function.identity()));
+    List<DexProgramClass> newMainDexClasses = new ArrayList<>();
+    mainDexClasses.forEach(
+        mainDexClass -> {
+          DexProgramClass definition = programClasses.get(mainDexClass);
+          if (definition == null) {
+            return;
+          }
+          Iterable<DexType> syntheticTypes =
+              readAnnotationSynthesizedClassMap(definition, appView.dexItemFactory());
+          for (DexType syntheticType : syntheticTypes) {
+            DexProgramClass syntheticClass = programClasses.get(syntheticType);
+            if (syntheticClass != null) {
+              newMainDexClasses.add(syntheticClass);
+            }
+          }
+        });
+    mainDexClasses.addAll(newMainDexClasses);
   }
 
   private void clearSynthesizedClassMapping(Builder<?> builder) {
@@ -558,8 +572,7 @@
           .stream()
           .map(dexProgramClass -> dexProgramClass.type)
           .forEach(synthesized::add);
-      synthesized.addAll(
-          DexAnnotation.readAnnotationSynthesizedClassMap(original, builder.dexItemFactory));
+      synthesized.addAll(readAnnotationSynthesizedClassMap(original, builder.dexItemFactory));
 
       DexAnnotation updatedAnnotation =
           DexAnnotation.createAnnotationSynthesizedClassMap(synthesized, builder.dexItemFactory);
@@ -589,52 +602,53 @@
 
   private void convertMethod(ProgramMethod method) {
     DexEncodedMethod definition = method.getDefinition();
-    if (definition.getCode() != null) {
-      boolean matchesMethodFilter = options.methodMatchesFilter(definition);
-      if (matchesMethodFilter) {
-        if (appView.options().enableNeverMergePrefixes) {
-          for (DexString neverMergePrefix : neverMergePrefixes) {
-            // Synthetic classes will always be merged.
-            if (appView.appInfo().getSyntheticItems().isSyntheticClass(method.getHolder())) {
-              continue;
-            }
-            if (method.getHolderType().descriptor.startsWith(neverMergePrefix)) {
-              seenNeverMergePrefix.getAndSet(true);
-            } else {
-              seenNotNeverMergePrefix.getAndSet(true);
-            }
-            // Don't mix.
-            if (seenNeverMergePrefix.get() && seenNotNeverMergePrefix.get()) {
-              StringBuilder message = new StringBuilder();
-              message
-                  .append("Merging dex file containing classes with prefix")
-                  .append(neverMergePrefixes.size() > 1 ? "es " : " ");
-              for (int i = 0; i < neverMergePrefixes.size(); i++) {
-                message
-                    .append("'")
-                    .append(neverMergePrefixes.get(0).toString().substring(1).replace('/', '.'))
-                    .append("'")
-                    .append(i < neverMergePrefixes.size() - 1 ? ", " : "");
-              }
-              message.append(" with classes with any other prefixes is not allowed.");
-              throw new CompilationError(message.toString());
-            }
-          }
+    if (definition.getCode() == null) {
+      return;
+    }
+    if (!options.methodMatchesFilter(definition)) {
+      return;
+    }
+    checkPrefixMerging(method);
+    if (options.isGeneratingClassFiles()
+        || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
+      // We do not process in call graph order, so anything could be a leaf.
+      rewriteCode(
+          method, simpleOptimizationFeedback, OneTimeMethodProcessor.create(method, appView), null);
+    } else {
+      assert definition.getCode().isDexCode();
+    }
+    if (!options.isGeneratingClassFiles()) {
+      updateHighestSortingStrings(definition);
+    }
+  }
+
+  private void checkPrefixMerging(ProgramMethod method) {
+    if (!appView.options().enableNeverMergePrefixes) {
+      return;
+    }
+    for (DexString neverMergePrefix : neverMergePrefixes) {
+      if (method.getHolderType().descriptor.startsWith(neverMergePrefix)) {
+        seenNeverMergePrefix.getAndSet(true);
+      } else {
+        seenNotNeverMergePrefix.getAndSet(true);
+      }
+      // Don't mix.
+      // TODO(b/168001352): Consider requiring that no 'never merge' prefix is ever seen as a
+      //  passthrough object.
+      if (seenNeverMergePrefix.get() && seenNotNeverMergePrefix.get()) {
+        StringBuilder message = new StringBuilder();
+        message
+            .append("Merging dex file containing classes with prefix")
+            .append(neverMergePrefixes.size() > 1 ? "es " : " ");
+        for (int i = 0; i < neverMergePrefixes.size(); i++) {
+          message
+              .append("'")
+              .append(neverMergePrefixes.get(0).toString().substring(1).replace('/', '.'))
+              .append("'")
+              .append(i < neverMergePrefixes.size() - 1 ? ", " : "");
         }
-        if (options.isGeneratingClassFiles()
-            || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
-          // We do not process in call graph order, so anything could be a leaf.
-          rewriteCode(
-              method,
-              simpleOptimizationFeedback,
-              OneTimeMethodProcessor.create(method, appView),
-              null);
-        } else {
-          assert definition.getCode().isDexCode();
-        }
-        if (!options.isGeneratingClassFiles()) {
-          updateHighestSortingStrings(definition);
-        }
+        message.append(" with classes with any other prefixes is not allowed.");
+        throw new CompilationError(message.toString());
       }
     }
   }
@@ -716,6 +730,9 @@
       assert graphLensForIR == appView.graphLens();
     }
 
+    // The field access info collection is not maintained during IR processing.
+    appView.appInfo().withLiveness().getFieldAccessInfoCollection().destroyAccessContexts();
+
     // Assure that no more optimization feedback left after primary processing.
     assert feedback.noUpdatesLeft();
     appView.setAllCodeProcessed();
@@ -789,7 +806,7 @@
     synthesizeTwrCloseResourceUtilityClass(builder, executorService);
     synthesizeJava8UtilityClass(builder, executorService);
     synthesizeRetargetClass(builder, executorService);
-    handleSynthesizedClassMapping(builder);
+    handleSynthesizedClassMapping(appView, builder);
     synthesizeEnumUnboxingUtilityMethods(builder, executorService);
 
     printPhase("Lambda merging finalization");
@@ -800,10 +817,10 @@
     generateDesugaredLibraryAPIWrappers(builder, executorService);
 
     if (serviceLoaderRewriter != null && serviceLoaderRewriter.getSynthesizedClass() != null) {
-      appView.appInfo().addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass());
+      appView.appInfo().addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass(), true);
       processSynthesizedServiceLoaderMethods(
           serviceLoaderRewriter.getSynthesizedClass(), executorService);
-      builder.addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass(), true);
+      builder.addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass());
     }
 
     // Update optimization info for all synthesized methods at once.
@@ -823,7 +840,7 @@
             },
             executorService);
         DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType());
-        appView.appInfo().addSynthesizedClass(outlineClass);
+        appView.appInfo().addSynthesizedClass(outlineClass, true);
         optimizeSynthesizedClass(outlineClass, executorService);
         forEachSelectedOutliningMethod(
             methodsSelectedForOutlining,
@@ -836,7 +853,7 @@
             executorService);
         feedback.updateVisibleOptimizationInfo();
         assert outliner.checkAllOutlineSitesFoundAgain();
-        builder.addSynthesizedClass(outlineClass, true);
+        builder.addSynthesizedClass(outlineClass);
         clearDexMethodCompilationState(outlineClass);
       }
       timing.end();
@@ -1239,7 +1256,7 @@
       assert appView.enableWholeProgramOptimizations();
       timing.begin("Collect optimization info");
       collectOptimizationInfo(
-          method, code, ClassInitializerDefaultsResult.empty(), feedback, methodProcessor, timing);
+          context, code, ClassInitializerDefaultsResult.empty(), feedback, methodProcessor, timing);
       timing.end();
       return timing;
     }
@@ -1601,7 +1618,7 @@
     if (appView.enableWholeProgramOptimizations()) {
       timing.begin("Collect optimization info");
       collectOptimizationInfo(
-          method, code, classInitializerDefaultsResult, feedback, methodProcessor, timing);
+          context, code, classInitializerDefaultsResult, feedback, methodProcessor, timing);
       timing.end();
     }
 
@@ -1636,7 +1653,7 @@
   // Compute optimization info summary for the current method unless it is pinned
   // (in that case we should not be making any assumptions about the behavior of the method).
   public void collectOptimizationInfo(
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
@@ -1658,7 +1675,8 @@
     }
 
     // Arguments can be changed during the debug mode.
-    boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+    boolean isDebugMode =
+        options.debug || method.getDefinition().getOptimizationInfo().isReachabilitySensitive();
     if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
       timing.begin("Collect call-site info");
       appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code, timing);
@@ -1670,8 +1688,8 @@
     }
 
     InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos = null;
-    if (method.isInitializer()) {
-      if (method.isClassInitializer()) {
+    if (method.getDefinition().isInitializer()) {
+      if (method.getDefinition().isClassInitializer()) {
         StaticFieldValueAnalysis.run(
             appView, code, classInitializerDefaultsResult, feedback, timing);
       } else {
@@ -1681,12 +1699,7 @@
       }
     }
     methodOptimizationInfoCollector.collectMethodOptimizationInfo(
-        code.method(),
-        code,
-        feedback,
-        dynamicTypeOptimization,
-        instanceFieldInitializationInfos,
-        timing);
+        method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos, timing);
   }
 
   public void removeDeadCodeAndFinalizeIR(
@@ -1744,7 +1757,7 @@
     timing.end();
   }
 
-  private void markProcessed(IRCode code, OptimizationFeedback feedback) {
+  public void markProcessed(IRCode code, OptimizationFeedback feedback) {
     // After all the optimizations have take place, we compute whether method should be inlined.
     ProgramMethod method = code.context();
     ConstraintWithTarget state =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 51905bf..576a376 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY;
 import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
@@ -41,13 +40,7 @@
 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.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -55,7 +48,6 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
-import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -101,7 +93,6 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiFunction;
 
 public class LensCodeRewriter {
@@ -109,11 +100,12 @@
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
   private final EnumUnboxer enumUnboxer;
-  private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
+  private final LensCodeRewriterUtils helper;
 
   LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
     this.enumUnboxer = enumUnboxer;
+    this.helper = new LensCodeRewriterUtils(appView);
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
@@ -152,7 +144,7 @@
             {
               InvokeCustom invokeCustom = current.asInvokeCustom();
               DexCallSite callSite = invokeCustom.getCallSite();
-              DexCallSite newCallSite = rewriteCallSite(callSite, method);
+              DexCallSite newCallSite = helper.rewriteCallSite(callSite, method);
               if (newCallSite != callSite) {
                 Value newOutValue = makeOutValue(invokeCustom, code);
                 InvokeCustom newInvokeCustom =
@@ -169,7 +161,7 @@
             {
               DexMethodHandle handle = current.asConstMethodHandle().getValue();
               DexMethodHandle newHandle =
-                  rewriteDexMethodHandle(handle, method, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+                  helper.rewriteDexMethodHandle(handle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, method);
               if (newHandle != handle) {
                 Value newOutValue = makeOutValue(current, code);
                 iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
@@ -632,28 +624,6 @@
     return TypeElement.getNull();
   }
 
-  public DexCallSite rewriteCallSite(DexCallSite callSite, ProgramMethod context) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexProto newMethodProto =
-        dexItemFactory.applyClassMappingToProto(
-            callSite.methodProto, appView.graphLens()::lookupType, protoFixupCache);
-    DexMethodHandle newBootstrapMethod =
-        rewriteDexMethodHandle(
-            callSite.bootstrapMethod, context, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
-    boolean isLambdaMetaFactory =
-        dexItemFactory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
-    MethodHandleUse methodHandleUse =
-        isLambdaMetaFactory ? ARGUMENT_TO_LAMBDA_METAFACTORY : NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
-    List<DexValue> newArgs = rewriteBootstrapArgs(callSite.bootstrapArgs, context, methodHandleUse);
-    if (!newMethodProto.equals(callSite.methodProto)
-        || newBootstrapMethod != callSite.bootstrapMethod
-        || !newArgs.equals(callSite.bootstrapArgs)) {
-      return dexItemFactory.createCallSite(
-          callSite.methodName, newMethodProto, newBootstrapMethod, newArgs);
-    }
-    return callSite;
-  }
-
   // If the given invoke is on the form "invoke-direct A.<init>, v0, ..." and the definition of
   // value v0 is "new-instance v0, B", where B is a subtype of A (see the Art800 and B116282409
   // tests), then fail with a compilation error if A has previously been merged into B.
@@ -731,116 +701,6 @@
     return !deadCatchHandlers.isEmpty();
   }
 
-  private List<DexValue> rewriteBootstrapArgs(
-      List<DexValue> bootstrapArgs, ProgramMethod method, MethodHandleUse use) {
-    List<DexValue> newBootstrapArgs = null;
-    boolean changed = false;
-    for (int i = 0; i < bootstrapArgs.size(); i++) {
-      DexValue argument = bootstrapArgs.get(i);
-      DexValue newArgument = null;
-      switch (argument.getValueKind()) {
-        case METHOD_HANDLE:
-          newArgument = rewriteDexValueMethodHandle(argument.asDexValueMethodHandle(), method, use);
-          break;
-        case METHOD_TYPE:
-          newArgument = rewriteDexMethodType(argument.asDexValueMethodType());
-          break;
-        case TYPE:
-          DexType oldType = argument.asDexValueType().value;
-          DexType newType = appView.graphLens().lookupType(oldType);
-          if (newType != oldType) {
-            newArgument = new DexValueType(newType);
-          }
-          break;
-        default:
-          // Intentionally empty.
-      }
-      if (newArgument != null) {
-        if (newBootstrapArgs == null) {
-          newBootstrapArgs = new ArrayList<>(bootstrapArgs.subList(0, i));
-        }
-        newBootstrapArgs.add(newArgument);
-        changed = true;
-      } else if (newBootstrapArgs != null) {
-        newBootstrapArgs.add(argument);
-      }
-    }
-    return changed ? newBootstrapArgs : bootstrapArgs;
-  }
-
-  private DexValueMethodHandle rewriteDexValueMethodHandle(
-      DexValueMethodHandle methodHandle, ProgramMethod context, MethodHandleUse use) {
-    DexMethodHandle oldHandle = methodHandle.value;
-    DexMethodHandle newHandle = rewriteDexMethodHandle(oldHandle, context, use);
-    return newHandle != oldHandle ? new DexValueMethodHandle(newHandle) : methodHandle;
-  }
-
-  private DexMethodHandle rewriteDexMethodHandle(
-      DexMethodHandle methodHandle, ProgramMethod context, MethodHandleUse use) {
-    if (methodHandle.isMethodHandle()) {
-      DexMethod invokedMethod = methodHandle.asMethod();
-      MethodHandleType oldType = methodHandle.type;
-      GraphLensLookupResult lensLookup =
-          appView
-              .graphLens()
-              .lookupMethod(invokedMethod, context.getReference(), oldType.toInvokeType());
-      DexMethod rewrittenTarget = lensLookup.getMethod();
-      DexMethod actualTarget;
-      MethodHandleType newType;
-      if (use == ARGUMENT_TO_LAMBDA_METAFACTORY) {
-        // Lambda metafactory arguments will be lambda desugared away and therefore cannot flow
-        // to a MethodHandle.invokeExact call. We can therefore member-rebind with no issues.
-        actualTarget = rewrittenTarget;
-        newType = lensLookup.getType().toMethodHandle(actualTarget);
-      } else {
-        assert use == NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
-        // MethodHandles that are not arguments to a lambda metafactory will not be desugared
-        // away. Therefore they could flow to a MethodHandle.invokeExact call which means that
-        // we cannot member rebind. We therefore keep the receiver and also pin the receiver
-        // with a keep rule (see Enqueuer.registerMethodHandle).
-        actualTarget =
-            appView
-                .dexItemFactory()
-                .createMethod(invokedMethod.holder, rewrittenTarget.proto, rewrittenTarget.name);
-        newType = oldType;
-        if (oldType.isInvokeDirect()) {
-          // For an invoke direct, the rewritten target must have the same holder as the original.
-          // If the method has changed from private to public we need to use virtual instead of
-          // direct.
-          assert rewrittenTarget.holder == actualTarget.holder;
-          newType = lensLookup.getType().toMethodHandle(actualTarget);
-          assert newType == MethodHandleType.INVOKE_DIRECT
-              || newType == MethodHandleType.INVOKE_INSTANCE;
-        }
-      }
-      if (newType != oldType || actualTarget != invokedMethod || rewrittenTarget != actualTarget) {
-        DexClass holder = appView.definitionFor(actualTarget.holder);
-        boolean isInterface = holder != null ? holder.isInterface() : methodHandle.isInterface;
-        return new DexMethodHandle(
-            newType,
-            actualTarget,
-            isInterface,
-            rewrittenTarget != actualTarget ? rewrittenTarget : null);
-      }
-    } else {
-      DexField field = methodHandle.asField();
-      DexField actualField = appView.graphLens().lookupField(field);
-      if (actualField != field) {
-        return new DexMethodHandle(methodHandle.type, actualField, methodHandle.isInterface);
-      }
-    }
-    return methodHandle;
-  }
-
-  private DexValueMethodType rewriteDexMethodType(DexValueMethodType type) {
-    DexProto oldProto = type.value;
-    DexProto newProto =
-        appView
-            .dexItemFactory()
-            .applyClassMappingToProto(oldProto, appView.graphLens()::lookupType, protoFixupCache);
-    return newProto != oldProto ? new DexValueMethodType(newProto) : type;
-  }
-
   class InstructionReplacer {
 
     private final IRCode code;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
new file mode 100644
index 0000000..27b3064
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -0,0 +1,180 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY;
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+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.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class LensCodeRewriterUtils {
+
+  private final DexDefinitionSupplier definitions;
+  private final GraphLens graphLens;
+
+  private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
+
+  public LensCodeRewriterUtils(AppView<?> appView) {
+    this(appView, appView.graphLens());
+  }
+
+  public LensCodeRewriterUtils(DexDefinitionSupplier definitions, GraphLens graphLens) {
+    this.definitions = definitions;
+    this.graphLens = graphLens;
+  }
+
+  public DexCallSite rewriteCallSite(DexCallSite callSite, ProgramMethod context) {
+    DexItemFactory dexItemFactory = definitions.dexItemFactory();
+    DexProto newMethodProto = rewriteProto(callSite.methodProto);
+    DexMethodHandle newBootstrapMethod =
+        rewriteDexMethodHandle(
+            callSite.bootstrapMethod, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context);
+    boolean isLambdaMetaFactory =
+        dexItemFactory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+    MethodHandleUse methodHandleUse =
+        isLambdaMetaFactory ? ARGUMENT_TO_LAMBDA_METAFACTORY : NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+    List<DexValue> newArgs =
+        rewriteBootstrapArguments(callSite.bootstrapArgs, methodHandleUse, context);
+    if (!newMethodProto.equals(callSite.methodProto)
+        || newBootstrapMethod != callSite.bootstrapMethod
+        || !newArgs.equals(callSite.bootstrapArgs)) {
+      return dexItemFactory.createCallSite(
+          callSite.methodName, newMethodProto, newBootstrapMethod, newArgs);
+    }
+    return callSite;
+  }
+
+  public DexMethodHandle rewriteDexMethodHandle(
+      DexMethodHandle methodHandle, MethodHandleUse use, ProgramMethod context) {
+    if (methodHandle.isMethodHandle()) {
+      DexMethod invokedMethod = methodHandle.asMethod();
+      MethodHandleType oldType = methodHandle.type;
+      GraphLensLookupResult lensLookup =
+          graphLens.lookupMethod(invokedMethod, context.getReference(), oldType.toInvokeType());
+      DexMethod rewrittenTarget = lensLookup.getMethod();
+      DexMethod actualTarget;
+      MethodHandleType newType;
+      if (use == ARGUMENT_TO_LAMBDA_METAFACTORY) {
+        // Lambda metafactory arguments will be lambda desugared away and therefore cannot flow
+        // to a MethodHandle.invokeExact call. We can therefore member-rebind with no issues.
+        actualTarget = rewrittenTarget;
+        newType = lensLookup.getType().toMethodHandle(actualTarget);
+      } else {
+        assert use == NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+        // MethodHandles that are not arguments to a lambda metafactory will not be desugared
+        // away. Therefore they could flow to a MethodHandle.invokeExact call which means that
+        // we cannot member rebind. We therefore keep the receiver and also pin the receiver
+        // with a keep rule (see Enqueuer.registerMethodHandle).
+        actualTarget =
+            definitions
+                .dexItemFactory()
+                .createMethod(invokedMethod.holder, rewrittenTarget.proto, rewrittenTarget.name);
+        newType = oldType;
+        if (oldType.isInvokeDirect()) {
+          // For an invoke direct, the rewritten target must have the same holder as the original.
+          // If the method has changed from private to public we need to use virtual instead of
+          // direct.
+          assert rewrittenTarget.holder == actualTarget.holder;
+          newType = lensLookup.getType().toMethodHandle(actualTarget);
+          assert newType == MethodHandleType.INVOKE_DIRECT
+              || newType == MethodHandleType.INVOKE_INSTANCE;
+        }
+      }
+      if (newType != oldType || actualTarget != invokedMethod || rewrittenTarget != actualTarget) {
+        DexClass holder = definitions.definitionFor(actualTarget.holder, context);
+        boolean isInterface = holder != null ? holder.isInterface() : methodHandle.isInterface;
+        return new DexMethodHandle(
+            newType,
+            actualTarget,
+            isInterface,
+            rewrittenTarget != actualTarget ? rewrittenTarget : null);
+      }
+    } else {
+      DexField field = methodHandle.asField();
+      DexField actualField = graphLens.lookupField(field);
+      if (actualField != field) {
+        return new DexMethodHandle(methodHandle.type, actualField, methodHandle.isInterface);
+      }
+    }
+    return methodHandle;
+  }
+
+  private List<DexValue> rewriteBootstrapArguments(
+      List<DexValue> bootstrapArgs, MethodHandleUse use, ProgramMethod context) {
+    List<DexValue> newBootstrapArgs = null;
+    boolean changed = false;
+    for (int i = 0; i < bootstrapArgs.size(); i++) {
+      DexValue argument = bootstrapArgs.get(i);
+      DexValue newArgument = rewriteBootstrapArgument(argument, use, context);
+      if (newArgument != argument) {
+        if (newBootstrapArgs == null) {
+          newBootstrapArgs = new ArrayList<>(bootstrapArgs.subList(0, i));
+        }
+        newBootstrapArgs.add(newArgument);
+        changed = true;
+      } else if (newBootstrapArgs != null) {
+        newBootstrapArgs.add(newArgument);
+      }
+    }
+    return changed ? newBootstrapArgs : bootstrapArgs;
+  }
+
+  private DexValueMethodType rewriteDexMethodType(DexValueMethodType type) {
+    DexProto oldProto = type.value;
+    DexProto newProto = rewriteProto(oldProto);
+    return newProto != oldProto ? new DexValueMethodType(newProto) : type;
+  }
+
+  private DexValue rewriteBootstrapArgument(
+      DexValue value, MethodHandleUse use, ProgramMethod context) {
+    switch (value.getValueKind()) {
+      case METHOD_HANDLE:
+        return rewriteDexValueMethodHandle(value.asDexValueMethodHandle(), use, context);
+      case METHOD_TYPE:
+        return rewriteDexMethodType(value.asDexValueMethodType());
+      case TYPE:
+        DexType oldType = value.asDexValueType().value;
+        DexType newType = graphLens.lookupType(oldType);
+        return newType != oldType ? new DexValueType(newType) : value;
+      default:
+        return value;
+    }
+  }
+
+  public DexProto rewriteProto(DexProto proto) {
+    return definitions
+        .dexItemFactory()
+        .applyClassMappingToProto(proto, graphLens::lookupType, protoFixupCache);
+  }
+
+  private DexValueMethodHandle rewriteDexValueMethodHandle(
+      DexValueMethodHandle methodHandle, MethodHandleUse use, ProgramMethod context) {
+    DexMethodHandle oldHandle = methodHandle.value;
+    DexMethodHandle newHandle = rewriteDexMethodHandle(oldHandle, use, context);
+    return newHandle != oldHandle ? new DexValueMethodHandle(newHandle) : methodHandle;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index e54ec6c..4379da1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -35,6 +35,8 @@
   void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 
+  void unsetAbstractReturnValue(DexEncodedMethod method);
+
   void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 347f495..5c08f1f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -221,9 +221,8 @@
               ParameterAnnotationsList.empty(),
               code,
               true);
-      boolean addToMainDexList =
-          referencingClasses.stream()
-              .anyMatch(clazz -> appView.appInfo().isInMainDexList(clazz.type));
+      boolean addToMainDexClasses =
+          appView.appInfo().getMainDexClasses().containsAnyOf(referencingClasses);
       DexProgramClass utilityClass =
           synthesizeClassWithUniqueMethod(
               builder,
@@ -231,7 +230,7 @@
               method.holder,
               dexEncodedMethod,
               "java8 methods utility class",
-              addToMainDexList,
+              addToMainDexClasses,
               appView);
       // The following may add elements to methodsProviders.
       converter.optimizeSynthesizedClass(utilityClass, executorService);
@@ -244,7 +243,7 @@
       DexType type,
       DexEncodedMethod uniqueMethod,
       String origin,
-      boolean addToMainDexList,
+      boolean addToMainDexClasses,
       AppView<?> appView) {
     DexItemFactory factory = appView.dexItemFactory();
     DexProgramClass newClass =
@@ -271,8 +270,8 @@
                 : new DexEncodedMethod[] {uniqueMethod},
             factory.getSkipNameValidationForTesting(),
             getChecksumSupplier(uniqueMethod, appView));
-    appView.appInfo().addSynthesizedClass(newClass);
-    builder.addSynthesizedClass(newClass, addToMainDexList);
+    appView.appInfo().addSynthesizedClass(newClass, addToMainDexClasses);
+    builder.addSynthesizedClass(newClass);
     return newClass;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 23a6465..7936ffd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -525,8 +525,8 @@
       Collection<DexProgramClass> wrappers)
       throws ExecutionException {
     for (DexProgramClass wrapper : wrappers) {
-      builder.addSynthesizedClass(wrapper, false);
-      appView.appInfo().addSynthesizedClass(wrapper);
+      builder.addSynthesizedClass(wrapper);
+      appView.appInfo().addSynthesizedClass(wrapper, false);
     }
     irConverter.optimizeSynthesizedClasses(wrappers, executorService);
   }
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 6f46dc6..aa15050 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
@@ -41,6 +41,7 @@
 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.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
@@ -532,17 +533,21 @@
   private void generateEmulateInterfaceLibrary(Builder<?> builder) {
     // 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 = appView.definitionFor(interfaceType);
+      DexClass theInterface = appInfo.definitionFor(interfaceType);
       if (theInterface == null) {
         warnMissingEmulatedInterface(interfaceType);
       } else if (theInterface.isProgramClass()) {
+        DexProgramClass theProgramInterface = theInterface.asProgramClass();
         DexProgramClass synthesizedClass =
             synthesizeEmulateInterfaceLibraryClass(
-                theInterface.asProgramClass(), emulatedInterfacesHierarchy);
+                theProgramInterface, emulatedInterfacesHierarchy);
         if (synthesizedClass != null) {
-          builder.addSynthesizedClass(synthesizedClass, isInMainDexList(interfaceType));
-          appView.appInfo().addSynthesizedClass(synthesizedClass);
+          builder.addSynthesizedClass(synthesizedClass);
+          appInfo.addSynthesizedClass(
+              synthesizedClass, mainDexClasses.contains(theProgramInterface));
         }
       }
     }
@@ -797,10 +802,6 @@
     return factory.createType(interfaceTypeDescriptor);
   }
 
-  private boolean isInMainDexList(DexType iface) {
-    return appView.appInfo().isInMainDexList(iface);
-  }
-
   // Represent a static interface method as a method of companion class.
   final DexMethod staticAsMethodOfCompanionClass(DexMethod method) {
     // No changes for static methods.
@@ -1000,13 +1001,18 @@
     // make original default methods abstract, remove bridge methods, create dispatch
     // classes if needed.
     AppInfo appInfo = appView.appInfo();
-    for (Entry<DexType, DexProgramClass> entry : processInterfaces(builder, flavour).entrySet()) {
-      // 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.
-      DexProgramClass synthesizedClass = entry.getValue();
-      builder.addSynthesizedClass(synthesizedClass, isInMainDexList(entry.getKey()));
-      appInfo.addSynthesizedClass(synthesizedClass);
-    }
+    MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
+    processInterfaces(builder, flavour)
+        .forEach(
+            (interfaceClass, synthesizedClass) -> {
+              // 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);
+            });
 
     if (appView.options().isDesugaredLibraryCompilation()) {
       renameEmulatedInterfaces();
@@ -1030,7 +1036,7 @@
         && clazz.isInterface() == mustBeInterface;
   }
 
-  private Map<DexType, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
+  private Map<DexClass, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
     InterfaceProcessorNestedGraphLens.Builder graphLensBuilder =
         InterfaceProcessorNestedGraphLens.builder();
     InterfaceProcessor processor = new InterfaceProcessor(appView, this);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 6864127..a792f06 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -59,7 +59,7 @@
   private final InterfaceMethodRewriter rewriter;
 
   // All created companion and dispatch classes indexed by interface type.
-  final Map<DexType, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
+  final Map<DexClass, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
 
   InterfaceProcessor(
       AppView<?> appView, InterfaceMethodRewriter rewriter) {
@@ -221,7 +221,7 @@
             rewriter.factory.getSkipNameValidationForTesting(),
             getChecksumSupplier(iface),
             Collections.singletonList(iface));
-    syntheticClasses.put(iface.type, companionClass);
+    syntheticClasses.put(iface, companionClass);
   }
 
   private ChecksumSupplier getChecksumSupplier(DexProgramClass iface) {
@@ -307,7 +307,7 @@
             rewriter.factory.getSkipNameValidationForTesting(),
             DexProgramClass::checksumFromType,
             callers);
-    syntheticClasses.put(iface.type, dispatchClass);
+    syntheticClasses.put(iface, dispatchClass);
     return dispatchClass;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 8deb4fc..104fc97 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -167,7 +167,7 @@
             synthesizeVirtualMethods(mainMethod),
             appView.dexItemFactory().getSkipNameValidationForTesting(),
             LambdaClass::computeChecksumForSynthesizedClass);
-    appView.appInfo().addSynthesizedClass(clazz);
+    appView.appInfo().addSynthesizedClass(clazz, false);
 
     // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
     // ModificationException we must use synchronization.
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 c4709c7..dd7889d 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
@@ -157,8 +157,8 @@
         knownLambdaClasses.values(), converter, executorService);
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
       DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
-      appView.appInfo().addSynthesizedClass(synthesizedClass);
-      builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
+      appView.appInfo().addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
+      builder.addSynthesizedClass(synthesizedClass);
     }
     optimizeSynthesizedClasses(converter, executorService);
   }
@@ -190,10 +190,6 @@
             LambdaDescriptor.infer(callSite, appView.appInfoForDesugaring(), context));
   }
 
-  private boolean isInMainDexList(DexType type) {
-    return appView.appInfo().isInMainDexList(type);
-  }
-
   // Returns a lambda class corresponding to the lambda descriptor and context,
   // creates the class if it does not yet exist.
   public LambdaClass getOrCreateLambdaClass(
@@ -224,7 +220,7 @@
       }
     }
     lambdaClass.addSynthesizedFrom(accessedFrom.getHolder());
-    if (isInMainDexList(accessedFrom.getHolderType())) {
+    if (appView.appInfo().getMainDexClasses().contains(accessedFrom.getHolder())) {
       lambdaClass.addToMainDexList.set(true);
     }
     return lambdaClass;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 67571b3..8370326 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -196,15 +196,11 @@
 
   void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
     if (nestConstructorUsed) {
-      appView.appInfo().addSynthesizedClass(nestConstructor);
-      builder.addSynthesizedClass(nestConstructor, true);
+      appView.appInfo().addSynthesizedClass(nestConstructor, true);
+      builder.addSynthesizedClass(nestConstructor);
     }
   }
 
-  public static boolean isNestConstructor(DexType type) {
-    return type.getName().equals(NEST_CONSTRUCTOR_NAME);
-  }
-
   private DexString computeMethodBridgeName(DexEncodedMethod method) {
     String methodName = method.method.name.toString();
     String fullName;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index 4043023..613fac6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.Collections;
@@ -161,10 +162,10 @@
 
     // Process created class and method.
     AppInfo appInfo = appView.appInfo();
-    boolean addToMainDexList =
-        referencingClasses.stream().anyMatch(clazz -> appInfo.isInMainDexList(clazz.type));
-    appInfo.addSynthesizedClass(utilityClass);
+    MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
+    boolean addToMainDexList = mainDexClasses.containsAnyOf(referencingClasses);
+    appInfo.addSynthesizedClass(utilityClass, addToMainDexList);
     converter.optimizeSynthesizedClass(utilityClass, executorService);
-    builder.addSynthesizedClass(utilityClass, addToMainDexList);
+    builder.addSynthesizedClass(utilityClass);
   }
 }
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 fc01ffe..611a249 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
@@ -61,7 +61,7 @@
 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.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
@@ -88,7 +88,7 @@
   private final Set<DexMethod> blacklist;
   private final LambdaMerger lambdaMerger;
   private final LensCodeRewriter lensCodeRewriter;
-  final MainDexClasses mainDexClasses;
+  final MainDexTracingResult mainDexClasses;
 
   // State for inlining methods which are known to be called twice.
   private boolean applyDoubleInlining = false;
@@ -101,7 +101,7 @@
 
   public Inliner(
       AppView<AppInfoWithLiveness> appView,
-      MainDexClasses mainDexClasses,
+      MainDexTracingResult mainDexClasses,
       LambdaMerger lambdaMerger,
       LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 3d7d397..54b7f8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -108,7 +108,7 @@
   private final Map<DexType, ProgramMethodSet> enumsUnboxingCandidates;
   private final Map<DexType, Set<DexField>> requiredEnumInstanceFieldData =
       new ConcurrentHashMap<>();
-  private final Set<DexType> enumsToUnboxWithPackageRequirement = Sets.newIdentityHashSet();
+  private final Set<DexProgramClass> enumsToUnboxWithPackageRequirement = Sets.newIdentityHashSet();
 
   private EnumUnboxingRewriter enumUnboxerRewriter;
 
@@ -440,17 +440,17 @@
     }
     Map<DexType, DexType> newMethodLocationMap = new IdentityHashMap<>();
     Map<String, DexProgramClass> packageToClassMap = new HashMap<>();
-    for (DexType toUnbox : enumsToUnboxWithPackageRequirement) {
-      String packageDescriptor = toUnbox.getPackageDescriptor();
+    for (DexProgramClass toUnbox : enumsToUnboxWithPackageRequirement) {
+      String packageDescriptor = toUnbox.getType().getPackageDescriptor();
       DexProgramClass syntheticClass = packageToClassMap.get(packageDescriptor);
       if (syntheticClass == null) {
         syntheticClass = synthesizeUtilityClassInPackage(packageDescriptor, appBuilder);
         packageToClassMap.put(packageDescriptor, syntheticClass);
       }
-      if (appView.appInfo().isInMainDexList(toUnbox)) {
-        appBuilder.addToMainDexList(syntheticClass.type);
+      if (appView.appInfo().getMainDexClasses().contains(toUnbox)) {
+        appView.appInfo().getMainDexClasses().add(syntheticClass);
       }
-      newMethodLocationMap.put(toUnbox, syntheticClass.type);
+      newMethodLocationMap.put(toUnbox.getType(), syntheticClass.getType());
     }
     enumsToUnboxWithPackageRequirement.clear();
     return newMethodLocationMap;
@@ -488,8 +488,8 @@
             DexEncodedMethod.EMPTY_ARRAY,
             factory.getSkipNameValidationForTesting(),
             DexProgramClass::checksumFromType);
-    appBuilder.addSynthesizedClass(syntheticClass, false);
-    appView.appInfo().addSynthesizedClass(syntheticClass);
+    appBuilder.addSynthesizedClass(syntheticClass);
+    appView.appInfo().addSynthesizedClass(syntheticClass, false);
     return syntheticClass;
   }
 
@@ -559,7 +559,7 @@
       if (classConstraint == Constraint.NEVER) {
         markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
       } else if (classConstraint == Constraint.PACKAGE) {
-        enumsToUnboxWithPackageRequirement.add(toUnbox);
+        enumsToUnboxWithPackageRequirement.add(enumClass);
       }
     }
   }
@@ -877,6 +877,10 @@
         if (singleTarget == factory.javaLangSystemMethods.identityHashCode) {
           return Reason.ELIGIBLE;
         }
+        if (singleTarget == factory.stringMembers.valueOf) {
+          requiredEnumInstanceFieldData(enumClass.type, factory.enumMembers.nameField);
+          return Reason.ELIGIBLE;
+        }
         if (singleTarget == factory.objectMembers.getClass
             && (!invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers())) {
           // This is a hidden null check.
@@ -1238,9 +1242,7 @@
                   });
           clazz.getMethodCollection().removeMethods(methodsToRemove);
         } else {
-          clazz
-              .getMethodCollection()
-              .replaceMethods(encodedMethod -> fixupEncodedMethod(encodedMethod));
+          clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
           fixupFields(clazz.staticFields(), clazz::setStaticField);
           fixupFields(clazz.instanceFields(), clazz::setInstanceField);
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index ca106ab..6c73aef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.ArrayAccess;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -205,15 +206,26 @@
             continue;
           }
         } else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
-          assert invokeStatic.inValues().size() == 1;
+          assert invokeStatic.arguments().size() == 1;
           Value argument = invokeStatic.getArgument(0);
           DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
           if (enumType != null) {
             invokeStatic.outValue().replaceUsers(argument);
             iterator.removeOrReplaceByDebugLocalRead();
           }
+        } else if (invokedMethod == factory.stringMembers.valueOf) {
+          assert invokeStatic.arguments().size() == 1;
+          Value argument = invokeStatic.getArgument(0);
+          DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+          if (enumType != null) {
+            DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumType);
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(
+                    stringValueOfMethod, invokeStatic.outValue(), invokeStatic.arguments()));
+            continue;
+          }
         } else if (invokedMethod == factory.objectsMethods.requireNonNull) {
-          assert invokeStatic.inValues().size() == 1;
+          assert invokeStatic.arguments().size() == 1;
           Value argument = invokeStatic.getArgument(0);
           DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
           if (enumType != null) {
@@ -221,7 +233,7 @@
                 iterator, invokeStatic, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
           }
         } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
-          assert invokeStatic.inValues().size() == 2;
+          assert invokeStatic.arguments().size() == 2;
           Value argument = invokeStatic.getArgument(0);
           DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
           if (enumType != null) {
@@ -407,7 +419,24 @@
             factory.createProto(field.type, factory.intType),
             methodName);
     utilityMethods.computeIfAbsent(
-        fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field));
+        fieldMethod, m -> synthesizeInstanceFieldMethod(m, enumType, field, null));
+    return fieldMethod;
+  }
+
+  private DexMethod computeStringValueOfUtilityMethod(DexType enumType) {
+    // TODO(b/167994636): remove duplication between instance field name read and this method.
+    assert enumsToUnbox.containsEnum(enumType);
+    String methodName = "string$valueOf$" + compatibleName(enumType);
+    DexMethod fieldMethod =
+        factory.createMethod(
+            factory.enumUnboxingUtilityType,
+            factory.createProto(factory.stringType, factory.intType),
+            methodName);
+    AbstractValue nullString =
+        appView.abstractValueFactory().createSingleStringValue(factory.createString("null"));
+    utilityMethods.computeIfAbsent(
+        fieldMethod,
+        m -> synthesizeInstanceFieldMethod(m, enumType, factory.enumMembers.nameField, nullString));
     return fieldMethod;
   }
 
@@ -458,9 +487,6 @@
     utilityClass.appendStaticFields(fields);
     utilityClass.addDirectMethods(requiredMethods);
     assert requiredMethods.stream().allMatch(DexEncodedMethod::isPublic);
-    if (utilityClassInMainDexList()) {
-      builder.addToMainDexList(Collections.singletonList(utilityClass.type));
-    }
     // TODO(b/147860220): Use processMethodsConcurrently on requiredMethods instead.
     converter.optimizeSynthesizedClass(utilityClass, executorService);
   }
@@ -489,7 +515,7 @@
   }
 
   private DexEncodedMethod synthesizeInstanceFieldMethod(
-      DexMethod method, DexType enumType, DexField field) {
+      DexMethod method, DexType enumType, DexField field, AbstractValue nullValue) {
     assert method.proto.returnType == field.type;
     assert unboxedEnumsInstanceFieldData.getInstanceFieldData(enumType, field).isMapping();
     CfCode cfCode =
@@ -500,7 +526,8 @@
                 enumsToUnbox.getEnumValueInfoMap(enumType),
                 unboxedEnumsInstanceFieldData
                     .getInstanceFieldData(enumType, field)
-                    .asEnumFieldMappingData())
+                    .asEnumFieldMappingData(),
+                nullValue)
             .generateCfCode();
     return synthesizeUtilityMethod(cfCode, method, false);
   }
@@ -523,16 +550,6 @@
     return synthesizeUtilityMethod(cfCode, method, false);
   }
 
-  // TODO(b/150178516): Add a test for this case.
-  private boolean utilityClassInMainDexList() {
-    for (DexType toUnbox : enumsToUnbox.enumSet()) {
-      if (appView.appInfo().isInMainDexList(toUnbox)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   private DexEncodedMethod synthesizeZeroCheckMethod() {
     CfCode cfCode =
         EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index b189a57..9125b1c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -82,6 +82,7 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
@@ -92,6 +93,7 @@
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
+import com.android.tools.r8.ir.optimize.typechecks.CheckCastAndInstanceOfMethodSpecialization;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -112,37 +114,46 @@
 import java.util.function.Predicate;
 
 public class MethodOptimizationInfoCollector {
-  private final AppView<AppInfoWithLiveness> appView;
-  private final InternalOptions options;
-  private final DexItemFactory dexItemFactory;
 
-  public MethodOptimizationInfoCollector(AppView<AppInfoWithLiveness> appView) {
+  private final AppView<AppInfoWithLiveness> appView;
+  private final CheckCastAndInstanceOfMethodSpecialization
+      checkCastAndInstanceOfMethodSpecialization;
+  private final DexItemFactory dexItemFactory;
+  private final InternalOptions options;
+
+  public MethodOptimizationInfoCollector(
+      AppView<AppInfoWithLiveness> appView, IRConverter converter) {
     this.appView = appView;
-    this.options = appView.options();
+    this.checkCastAndInstanceOfMethodSpecialization =
+        appView.options().isRelease()
+            ? new CheckCastAndInstanceOfMethodSpecialization(appView, converter)
+            : null;
     this.dexItemFactory = appView.dexItemFactory();
+    this.options = appView.options();
   }
 
   public void collectMethodOptimizationInfo(
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
       DynamicTypeOptimization dynamicTypeOptimization,
       InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
       Timing timing) {
-    identifyBridgeInfo(method, code, feedback, timing);
+    DexEncodedMethod definition = method.getDefinition();
+    identifyBridgeInfo(definition, code, feedback, timing);
     identifyClassInlinerEligibility(code, feedback, timing);
-    identifyParameterUsages(method, code, feedback, timing);
-    identifyReturnsArgument(code, feedback, timing);
+    identifyParameterUsages(definition, code, feedback, timing);
+    analyzeReturns(code, feedback, timing);
     if (options.enableInlining) {
-      identifyInvokeSemanticsForInlining(method, code, feedback, timing);
+      identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
-    computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code, timing);
-    computeInitializedClassesOnNormalExit(feedback, method, code, timing);
+    computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
+    computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
     computeInstanceInitializerInfo(
-        method, code, feedback, instanceFieldInitializationInfos, timing);
-    computeMayHaveSideEffects(feedback, method, code, timing);
-    computeReturnValueOnlyDependsOnArguments(feedback, method, code, timing);
-    computeNonNullParamOrThrow(feedback, method, code, timing);
+        definition, code, feedback, instanceFieldInitializationInfos, timing);
+    computeMayHaveSideEffects(feedback, definition, code, timing);
+    computeReturnValueOnlyDependsOnArguments(feedback, definition, code, timing);
+    computeNonNullParamOrThrow(feedback, definition, code, timing);
     computeNonNullParamOnNormalExits(feedback, code, timing);
   }
 
@@ -345,13 +356,13 @@
     return builder.build();
   }
 
-  private void identifyReturnsArgument(IRCode code, OptimizationFeedback feedback, Timing timing) {
+  private void analyzeReturns(IRCode code, OptimizationFeedback feedback, Timing timing) {
     timing.begin("Identify returns argument");
-    identifyReturnsArgument(code, feedback);
+    analyzeReturns(code, feedback);
     timing.end();
   }
 
-  private void identifyReturnsArgument(IRCode code, OptimizationFeedback feedback) {
+  private void analyzeReturns(IRCode code, OptimizationFeedback feedback) {
     ProgramMethod context = code.context();
     DexEncodedMethod method = context.getDefinition();
     List<BasicBlock> normalExits = code.computeNormalExitBlocks();
@@ -381,6 +392,10 @@
         AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context);
         if (abstractReturnValue.isNonTrivial()) {
           feedback.methodReturnsAbstractValue(method, appView, abstractReturnValue);
+          if (checkCastAndInstanceOfMethodSpecialization != null) {
+            checkCastAndInstanceOfMethodSpecialization.addCandidateForOptimization(
+                context, abstractReturnValue);
+          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index d9db007..07b7ce1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -189,6 +189,11 @@
   }
 
   @Override
+  public synchronized void unsetAbstractReturnValue(DexEncodedMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetAbstractReturnValue();
+  }
+
+  @Override
   public synchronized void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type) {
     getMethodOptimizationInfoForUpdating(method).markReturnsObjectWithUpperBoundType(appView, type);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 49ffaf9..6a28331 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -76,6 +76,9 @@
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {}
 
   @Override
+  public void unsetAbstractReturnValue(DexEncodedMethod method) {}
+
+  @Override
   public void methodReturnsObjectWithUpperBoundType(
       DexEncodedMethod method, AppView<?> appView, TypeElement type) {}
 
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 62edebf..9a3bb51 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
@@ -98,7 +98,12 @@
   @Override
   public void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
-    // Ignored.
+    method.getMutableOptimizationInfo().markReturnsAbstractValue(value);
+  }
+
+  @Override
+  public void unsetAbstractReturnValue(DexEncodedMethod method) {
+    method.getMutableOptimizationInfo().unsetAbstractReturnValue();
   }
 
   @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 610bad0..6d9fc9f 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
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
@@ -406,6 +407,10 @@
     abstractReturnValue = value;
   }
 
+  void unsetAbstractReturnValue() {
+    abstractReturnValue = UnknownValue.getInstance();
+  }
+
   void markReturnsObjectWithUpperBoundType(AppView<?> appView, TypeElement type) {
     assert type != null;
     // We may get more precise type information if the method is reprocessed (e.g., due to
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
index b80283b..0f6a8ed 100644
--- 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
@@ -16,6 +16,7 @@
 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.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import com.google.common.io.BaseEncoding;
@@ -58,6 +59,10 @@
       this.id = id;
       this.clazz = clazz;
     }
+
+    public DexProgramClass getLambdaClass() {
+      return clazz;
+    }
   }
 
   public LambdaGroup(LambdaGroupId id) {
@@ -93,8 +98,9 @@
   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.
-    for (DexType type : lambdas.keySet()) {
-      if (appView.appInfo().isInMainDexList(type)) {
+    MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
+    for (LambdaInfo info : lambdas.values()) {
+      if (mainDexClasses.contains(info.getLambdaClass())) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index a716bd4..a7b1576 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -367,8 +367,10 @@
     // Add synthesized lambda group classes to the builder.
     for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
       DexProgramClass synthesizedClass = entry.getValue();
-      appView.appInfo().addSynthesizedClass(synthesizedClass);
-      builder.addSynthesizedClass(synthesizedClass, entry.getKey().shouldAddToMainDex(appView));
+      appView
+          .appInfo()
+          .addSynthesizedClass(synthesizedClass, entry.getKey().shouldAddToMainDex(appView));
+      builder.addSynthesizedClass(synthesizedClass);
       // 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
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 6ad81c8..06a3b1a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -402,7 +402,7 @@
       OptimizationFeedback feedback) {
     return (code, methodProcessor) ->
         converter.collectOptimizationInfo(
-            code.method(),
+            code.context(),
             code,
             ClassInitializerDefaultsResult.empty(),
             feedback,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
new file mode 100644
index 0000000..a8f3018
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.typechecks;
+
+import static com.android.tools.r8.graph.AccessControl.isClassAccessible;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+
+/**
+ * An optimization that merges a method override (B.m()) into the method it overrides (A.m()).
+ *
+ * <p>If the method and its override effectively implement an 'instanceof B' instruction, i.e.,
+ * A.m() returns false and B.m() returns true, then B.m() is removed and the body of A.m() is
+ * updated to {@code return this instanceof B}.
+ *
+ * <p>If the method and its override implement 'not instanceof B', then B.m() is removed and the
+ * body of A.m() is updated to {@code return !(this instance B)}.
+ *
+ * <p>TODO(b/151596599): Also handle methods that implement casts.
+ */
+public class CheckCastAndInstanceOfMethodSpecialization implements Action {
+
+  private static final OptimizationFeedbackSimple feedback =
+      OptimizationFeedbackSimple.getInstance();
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final IRConverter converter;
+
+  private final ProgramMethodSet candidatesForInstanceOfOptimization =
+      ProgramMethodSet.createSorted();
+
+  public CheckCastAndInstanceOfMethodSpecialization(
+      AppView<AppInfoWithLiveness> appView, IRConverter converter) {
+    assert !appView.options().debug;
+    this.appView = appView;
+    this.converter = converter;
+  }
+
+  public void addCandidateForOptimization(ProgramMethod method, AbstractValue abstractReturnValue) {
+    if (!converter.isInWave()) {
+      return;
+    }
+    if (isCandidateForInstanceOfOptimization(method, abstractReturnValue)) {
+      synchronized (this) {
+        if (candidatesForInstanceOfOptimization.isEmpty()) {
+          converter.addWaveDoneAction(this);
+        }
+        candidatesForInstanceOfOptimization.add(method);
+      }
+    }
+  }
+
+  private boolean isCandidateForInstanceOfOptimization(
+      ProgramMethod method, AbstractValue abstractReturnValue) {
+    return method.getReference().getReturnType().isBooleanType()
+        && abstractReturnValue.isSingleBoolean();
+  }
+
+  @Override
+  public void execute() {
+    assert !candidatesForInstanceOfOptimization.isEmpty();
+    ProgramMethodSet processed = ProgramMethodSet.create();
+    for (ProgramMethod method : candidatesForInstanceOfOptimization) {
+      if (!processed.contains(method)) {
+        processCandidateForInstanceOfOptimization(method);
+      }
+    }
+  }
+
+  private void processCandidateForInstanceOfOptimization(ProgramMethod method) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (!definition.isNonPrivateVirtualMethod()) {
+      return;
+    }
+
+    MethodOptimizationInfo optimizationInfo = method.getDefinition().getOptimizationInfo();
+    if (optimizationInfo.mayHaveSideEffects()) {
+      return;
+    }
+
+    AbstractValue abstractReturnValue = optimizationInfo.getAbstractReturnValue();
+    if (!abstractReturnValue.isSingleBoolean()) {
+      return;
+    }
+
+    ProgramMethod parentMethod = resolveOnSuperClass(method);
+    if (parentMethod == null || !parentMethod.getDefinition().isNonPrivateVirtualMethod()) {
+      return;
+    }
+
+    MethodOptimizationInfo parentOptimizationInfo =
+        parentMethod.getDefinition().getOptimizationInfo();
+    if (parentOptimizationInfo.mayHaveSideEffects()) {
+      return;
+    }
+
+    AbstractValue abstractParentReturnValue = parentOptimizationInfo.getAbstractReturnValue();
+    if (!abstractParentReturnValue.isSingleBoolean()) {
+      return;
+    }
+
+    // Verify that the methods are not pinned. They shouldn't be, since we've computed an abstract
+    // return value for both.
+    assert !appView.appInfo().isPinned(method.getReference());
+    assert !appView.appInfo().isPinned(parentMethod.getReference());
+
+    if (appView
+        .appInfo()
+        .getObjectAllocationInfoCollection()
+        .hasInstantiatedStrictSubtype(method.getHolder())) {
+      return;
+    }
+
+    DexEncodedMethod parentMethodDefinition = parentMethod.getDefinition();
+    if (abstractParentReturnValue == abstractReturnValue) {
+      // The parent method is already guaranteed to return the same value.
+    } else if (isClassAccessible(method.getHolder(), parentMethod, appView).isTrue()) {
+      parentMethodDefinition.setCode(
+          parentMethodDefinition.buildInstanceOfCode(
+              method.getHolderType(), abstractParentReturnValue.isTrue(), appView.options()),
+          appView);
+      // Rebuild inlining constraints.
+      IRCode code =
+          parentMethodDefinition.getCode().buildIR(parentMethod, appView, parentMethod.getOrigin());
+      converter.markProcessed(code, feedback);
+      // Fixup method optimization info (the method no longer returns a constant).
+      feedback.unsetAbstractReturnValue(parentMethod.getDefinition());
+    } else {
+      return;
+    }
+
+    method.getHolder().removeMethod(method.getReference());
+  }
+
+  private ProgramMethod resolveOnSuperClass(ProgramMethod method) {
+    DexProgramClass superClass =
+        asProgramClassOrNull(appView.definitionFor(method.getHolder().superType));
+    if (superClass == null) {
+      return null;
+    }
+
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOn(superClass, method.getReference()).asSingleResolution();
+    if (resolutionResult == null) {
+      return null;
+    }
+    return resolutionResult.getResolvedProgramMethod();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index cb1823a..5036205 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -30,6 +32,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -234,6 +238,11 @@
       DexItemFactory factory = appView.dexItemFactory();
       List<CfInstruction> instructions = new ArrayList<>();
 
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(argType))
+              .build();
+
       // if (arg == null) { return null };
       CfLabel nullDest = new CfLabel();
       instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
@@ -241,6 +250,7 @@
       instructions.add(new CfConstNull());
       instructions.add(new CfReturn(ValueType.OBJECT));
       instructions.add(nullDest);
+      instructions.add(new CfFrame(locals, ImmutableList.of()));
 
       // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
       assert reverseWrapperField != null;
@@ -254,6 +264,7 @@
           new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
       instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
       instructions.add(unwrapDest);
+      instructions.add(new CfFrame(locals, ImmutableList.of()));
 
       // return new Wrapper(wrappedValue);
       instructions.add(new CfNew(wrapperField.holder));
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
index 3cb4210..e0579ed 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.ir.synthetic;
 
 import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -20,6 +22,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -53,6 +57,15 @@
     }
     int nextLabel = 0;
 
+    ImmutableInt2ReferenceSortedMap.Builder<FrameType> localsBuilder =
+        ImmutableInt2ReferenceSortedMap.builder();
+    localsBuilder.put(0, FrameType.initialized(interfaceType));
+    int index = 1;
+    for (DexType param : libraryMethod.proto.parameters.values) {
+      localsBuilder.put(index++, FrameType.initialized(param));
+    }
+    ImmutableInt2ReferenceSortedMap<FrameType> locals = localsBuilder.build();
+
     instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
     instructions.add(new CfInstanceOf(libraryMethod.holder));
     instructions.add(new CfIf(If.Type.EQ, ValueType.INT, labels[nextLabel]));
@@ -68,6 +81,7 @@
     for (Pair<DexType, DexMethod> dispatch : extraDispatchCases) {
       // Type check basic block.
       instructions.add(labels[nextLabel++]);
+      instructions.add(new CfFrame(locals, ImmutableList.of()));
       instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
       instructions.add(new CfInstanceOf(dispatch.getFirst()));
       instructions.add(new CfIf(If.Type.EQ, ValueType.INT, labels[nextLabel]));
@@ -82,6 +96,7 @@
 
     // Branch with companion call.
     instructions.add(labels[nextLabel]);
+    instructions.add(new CfFrame(locals, ImmutableList.of()));
     instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
     loadExtraParameters(instructions);
     instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, companionMethod, false));
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 8b35793..52cdb30 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -31,6 +33,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -62,21 +66,26 @@
     private final DexType returnType;
     private final EnumValueInfoMap enumValueInfoMap;
     private final EnumInstanceFieldMappingData fieldDataMap;
+    private final AbstractValue nullValue;
 
     public EnumUnboxingInstanceFieldCfCodeProvider(
         AppView<?> appView,
         DexType holder,
         DexType returnType,
         EnumValueInfoMap enumValueInfoMap,
-        EnumInstanceFieldMappingData fieldDataMap) {
+        EnumInstanceFieldMappingData fieldDataMap,
+        AbstractValue nullValue) {
       super(appView, holder);
       this.returnType = returnType;
       this.enumValueInfoMap = enumValueInfoMap;
       this.fieldDataMap = fieldDataMap;
+      this.nullValue = nullValue;
     }
 
     @Override
     public CfCode generateCfCode() {
+      // TODO(b/167942775): Should use a table-switch for large enums (maybe same threshold in the
+      //  rewriter of switchmaps).
       // Generated static method, for class com.x.MyEnum {A(10),B(20);} would look like:
       // String UtilityClass#com.x.MyEnum_toString(int i) {
       // if (i == 1) { return 10;}
@@ -85,6 +94,11 @@
       DexItemFactory factory = appView.dexItemFactory();
       List<CfInstruction> instructions = new ArrayList<>();
 
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(factory.intType))
+              .build();
+
       // if (i == 1) { return 10;}
       // if (i == 2) { return 20;}
       enumValueInfoMap.forEach(
@@ -98,12 +112,19 @@
               addCfInstructionsForAbstractValue(instructions, value, returnType);
               instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
               instructions.add(dest);
+              instructions.add(new CfFrame(locals, ImmutableList.of()));
             }
           });
 
-      // throw null;
-      instructions.add(new CfConstNull());
-      instructions.add(new CfThrow());
+      if (nullValue != null) {
+        // return "null"
+        addCfInstructionsForAbstractValue(instructions, nullValue, returnType);
+        instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
+      } else {
+        // throw null;
+        instructions.add(new CfConstNull());
+        instructions.add(new CfThrow());
+      }
 
       return standardCfCodeFromInstructions(instructions);
     }
@@ -139,6 +160,11 @@
       DexItemFactory factory = appView.dexItemFactory();
       List<CfInstruction> instructions = new ArrayList<>();
 
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(factory.stringType))
+              .build();
+
       // if (s == null) { throw npe("Name is null"); }
       CfLabel nullDest = new CfLabel();
       instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
@@ -150,6 +176,7 @@
           new CfInvoke(Opcodes.INVOKESPECIAL, factory.npeMethods.initWithMessage, false));
       instructions.add(new CfThrow());
       instructions.add(nullDest);
+      instructions.add(new CfFrame(locals, ImmutableList.of()));
 
       // if (s.equals("A")) { return 1;}
       // if (s.equals("B")) { return 2;}
@@ -165,6 +192,7 @@
             instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
             instructions.add(new CfReturn(ValueType.INT));
             instructions.add(dest);
+            instructions.add(new CfFrame(locals, ImmutableList.of()));
           });
 
       // throw new IllegalArgumentException("No enum constant com.x.MyEnum." + s);
@@ -225,6 +253,9 @@
       instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, initializationMethod, false));
       instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, utilityField, utilityField));
       instructions.add(nullDest);
+      instructions.add(
+          new CfFrame(
+              ImmutableInt2ReferenceSortedMap.<FrameType>builder().build(), ImmutableList.of()));
       instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
       instructions.add(new CfReturn(ValueType.OBJECT));
       return standardCfCodeFromInstructions(instructions);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 8c895f7..ec34d75 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.errors.Unimplemented;
 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.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -36,6 +37,8 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.references.Reference;
@@ -108,12 +111,13 @@
     }
     Optional<String> markerString =
         marker.isRelocator() ? Optional.empty() : Optional.of(marker.toString());
+    LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
     for (DexProgramClass clazz : application.classes()) {
       if (clazz.getSynthesizedFrom().isEmpty()
           || options.isDesugaredLibraryCompilation()
           || options.cfToCfDesugar) {
         try {
-          writeClass(clazz, consumer, markerString);
+          writeClass(clazz, consumer, rewriter, markerString);
         } catch (ClassTooLargeException e) {
           throw appView
               .options()
@@ -145,7 +149,10 @@
   }
 
   private void writeClass(
-      DexProgramClass clazz, ClassFileConsumer consumer, Optional<String> markerString) {
+      DexProgramClass clazz,
+      ClassFileConsumer consumer,
+      LensCodeRewriterUtils rewriter,
+      Optional<String> markerString) {
     ClassWriter writer = new ClassWriter(0);
     if (markerString.isPresent()) {
       int markerStringPoolIndex = writer.newConst(markerString.get());
@@ -194,12 +201,7 @@
     for (DexEncodedField field : clazz.instanceFields()) {
       writeField(field, writer);
     }
-    for (DexEncodedMethod method : clazz.directMethods()) {
-      writeMethod(method, writer, defaults, version);
-    }
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      writeMethod(method, writer, defaults, version);
-    }
+    clazz.forEachProgramMethod(method -> writeMethod(method, version, rewriter, writer, defaults));
     writer.visitEnd();
 
     byte[] result = writer.toByteArray();
@@ -323,28 +325,30 @@
   }
 
   private void writeMethod(
-      DexEncodedMethod method,
+      ProgramMethod method,
+      int classFileVersion,
+      LensCodeRewriterUtils rewriter,
       ClassWriter writer,
-      ImmutableMap<DexString, DexValue> defaults,
-      int classFileVersion) {
-    int access = method.accessFlags.getAsCfAccessFlags();
-    String name = namingLens.lookupName(method.method).toString();
-    String desc = method.descriptor(namingLens);
-    String signature = getSignature(method.annotations());
-    String[] exceptions = getExceptions(method.annotations());
+      ImmutableMap<DexString, DexValue> defaults) {
+    DexEncodedMethod definition = method.getDefinition();
+    int access = definition.getAccessFlags().getAsCfAccessFlags();
+    String name = namingLens.lookupName(method.getReference()).toString();
+    String desc = definition.descriptor(namingLens);
+    String signature = getSignature(definition.annotations());
+    String[] exceptions = getExceptions(definition.annotations());
     MethodVisitor visitor = writer.visitMethod(access, name, desc, signature, exceptions);
-    if (defaults.containsKey(method.method.name)) {
+    if (defaults.containsKey(definition.getName())) {
       AnnotationVisitor defaultVisitor = visitor.visitAnnotationDefault();
       if (defaultVisitor != null) {
-        writeAnnotationElement(defaultVisitor, null, defaults.get(method.method.name));
+        writeAnnotationElement(defaultVisitor, null, defaults.get(definition.getName()));
         defaultVisitor.visitEnd();
       }
     }
-    writeMethodParametersAnnotation(visitor, method.annotations().annotations);
-    writeAnnotations(visitor::visitAnnotation, method.annotations().annotations);
-    writeParameterAnnotations(visitor, method.parameterAnnotationsList);
-    if (!method.shouldNotHaveCode()) {
-      writeCode(method, visitor, classFileVersion);
+    writeMethodParametersAnnotation(visitor, definition.annotations().annotations);
+    writeAnnotations(visitor::visitAnnotation, definition.annotations().annotations);
+    writeParameterAnnotations(visitor, definition.parameterAnnotationsList);
+    if (!definition.shouldNotHaveCode()) {
+      writeCode(method, classFileVersion, rewriter, visitor);
     }
     visitor.visitEnd();
   }
@@ -478,8 +482,13 @@
     }
   }
 
-  private void writeCode(DexEncodedMethod method, MethodVisitor visitor, int classFileVersion) {
-    method.getCode().asCfCode().write(method, visitor, namingLens, appView, classFileVersion);
+  private void writeCode(
+      ProgramMethod method,
+      int classFileVersion,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    CfCode code = method.getDefinition().getCode().asCfCode();
+    code.write(method, classFileVersion, appView, namingLens, rewriter, visitor);
   }
 
   public static String printCf(byte[] result) {
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 05c6f6c..6c6bab5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
-import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
@@ -118,7 +117,7 @@
    * a given field is read/written by the program, and it also includes all indirect accesses to
    * each field. The latter is used, for example, during member rebinding.
    */
-  private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
+  private FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
   /** Information about instantiated classes and their allocation sites. */
   private final ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection;
   /** Set of all methods referenced in virtual invokes, along with calling context. */
@@ -197,6 +196,7 @@
   // TODO(zerny): Clean up the constructors so we have just one.
   AppInfoWithLiveness(
       DirectMappedDexApplication application,
+      MainDexClasses mainDexClasses,
       SyntheticItems.CommittedItems syntheticItems,
       Set<DexType> deadProtoTypes,
       Set<DexType> missingTypes,
@@ -238,7 +238,7 @@
       EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> constClassReferences,
       Map<DexType, Visibility> initClassReferences) {
-    super(application, syntheticItems);
+    super(application, mainDexClasses, syntheticItems);
     this.deadProtoTypes = deadProtoTypes;
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
@@ -325,6 +325,7 @@
       Map<DexType, Visibility> initClassReferences) {
     super(
         appInfoWithClassHierarchy.app(),
+        appInfoWithClassHierarchy.getMainDexClasses(),
         appInfoWithClassHierarchy.getSyntheticItems().commit(appInfoWithClassHierarchy.app()));
     this.deadProtoTypes = deadProtoTypes;
     this.missingTypes = missingTypes;
@@ -420,6 +421,7 @@
       Collection<DexReference> additionalPinnedItems) {
     this(
         application,
+        previous.getMainDexClasses(),
         previous.getSyntheticItems().commit(application),
         previous.deadProtoTypes,
         previous.missingTypes,
@@ -507,7 +509,10 @@
       AppInfoWithLiveness previous,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps) {
-    super(previous.app(), previous.getSyntheticItems().commit(previous.app()));
+    super(
+        previous.app(),
+        previous.getMainDexClasses(),
+        previous.getSyntheticItems().commit(previous.app()));
     this.deadProtoTypes = previous.deadProtoTypes;
     this.missingTypes = previous.missingTypes;
     this.liveTypes = previous.liveTypes;
@@ -715,25 +720,6 @@
     return constClassReferences.contains(type);
   }
 
-  public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
-    assert checkIfObsolete();
-    if (noLongerWrittenFields.isEmpty()) {
-      return this;
-    }
-    AppInfoWithLiveness result = new AppInfoWithLiveness(this);
-    result.fieldAccessInfoCollection.forEach(
-        info -> {
-          if (noLongerWrittenFields.contains(info.getField())) {
-            // Note that this implicitly mutates the current AppInfoWithLiveness, since the `info`
-            // instance is shared between the old and the new AppInfoWithLiveness. This should not
-            // lead to any problems, though, since the new AppInfo replaces the old AppInfo (we
-            // never use an obsolete AppInfo).
-            info.clearWrites();
-          }
-        });
-    return result;
-  }
-
   public Set<DexType> getDeadProtoTypes() {
     return deadProtoTypes;
   }
@@ -810,7 +796,7 @@
   public boolean isFieldRead(DexEncodedField encodedField) {
     assert checkIfObsolete();
     DexField field = encodedField.field;
-    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
+    FieldAccessInfo info = getFieldAccessInfoCollection().get(field);
     if (info != null && info.isRead()) {
       return true;
     }
@@ -834,7 +820,7 @@
   public boolean isFieldWrittenByFieldPutInstruction(DexEncodedField encodedField) {
     assert checkIfObsolete();
     DexField field = encodedField.field;
-    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
+    FieldAccessInfo info = getFieldAccessInfoCollection().get(field);
     if (info != null && info.isWritten()) {
       // The field is written directly by the program itself.
       return true;
@@ -850,13 +836,13 @@
   public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
-    if (!isPinned(field.field)) {
-      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
-      return fieldAccessInfo != null
-          && fieldAccessInfo.isWritten()
-          && !fieldAccessInfo.isWrittenOutside(method);
+    if (isPinned(field.field)) {
+      return false;
     }
-    return false;
+    FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.field);
+    return fieldAccessInfo != null
+        && fieldAccessInfo.isWritten()
+        && !fieldAccessInfo.isWrittenOutside(method);
   }
 
   public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexEncodedField field) {
@@ -865,7 +851,7 @@
     if (isPinned(field.field)) {
       return false;
     }
-    FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+    FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.field);
     if (fieldAccessInfo == null || !fieldAccessInfo.isWritten()) {
       return false;
     }
@@ -1004,6 +990,7 @@
     DexDefinitionSupplier definitionSupplier = application.getDefinitionsSupplier(committedItems);
     return new AppInfoWithLiveness(
         application,
+        getMainDexClasses().rewrittenWithLens(lens),
         committedItems,
         deadProtoTypes,
         missingTypes,
@@ -1015,7 +1002,9 @@
         lens.rewriteMethods(methodsTargetedByInvokeDynamic),
         lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
         lens.rewriteMethods(liveMethods),
-        fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
+        fieldAccessInfoCollection != null
+            ? fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens)
+            : null,
         objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens),
         rewriteInvokesWithContexts(virtualInvokes, lens),
         rewriteInvokesWithContexts(interfaceInvokes, lens),
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 ac6bad1..d692cef 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2792,7 +2792,7 @@
         new ArrayList<>();
 
     // Subset of synthesized classes that need to be added to the main-dex file.
-    Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
+    Set<DexProgramClass> mainDexTypes = Sets.newIdentityHashSet();
 
     boolean isEmpty() {
       boolean empty =
@@ -2808,7 +2808,7 @@
       assert !syntheticInstantiations.containsKey(clazz.type);
       syntheticInstantiations.put(clazz.type, new Pair<>(clazz, context));
       if (isMainDexClass) {
-        mainDexTypes.add(clazz.type);
+        mainDexTypes.add(clazz);
       }
     }
 
@@ -2836,7 +2836,11 @@
         appBuilder.addProgramClass(clazzAndContext.getFirst());
       }
       appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
-      appBuilder.addToMainDexList(mainDexTypes);
+    }
+
+    void amendMainDexClasses(MainDexClasses mainDexClasses) {
+      assert !isEmpty();
+      mainDexClasses.addAll(mainDexTypes);
     }
 
     void enqueueWorkItems(Enqueuer enqueuer) {
@@ -2887,6 +2891,7 @@
               additions.amendApplication(appBuilder);
               return appBuilder.build();
             });
+    additions.amendMainDexClasses(appInfo.getMainDexClasses());
     appView.setAppInfo(appInfo);
     subtypingInfo = new SubtypingInfo(appView);
 
@@ -3015,6 +3020,7 @@
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             app,
+            appInfo.getMainDexClasses(),
             appInfo.getSyntheticItems().commit(app),
             deadProtoTypes,
             mode.isFinalTreeShaking()
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
index c21ce6c..cfadae32 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
@@ -1,115 +1,100 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.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.google.common.collect.ImmutableSet;
+import com.android.tools.r8.graph.GraphLens;
 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 MainDexClasses {
 
-  public static MainDexClasses NONE = new MainDexClasses(ImmutableSet.of(), ImmutableSet.of());
+  private final Set<DexType> mainDexClasses;
 
-  public static class Builder {
-    public final AppInfo appInfo;
-    public final Set<DexType> roots = Sets.newIdentityHashSet();
-    public final Set<DexType> dependencies = Sets.newIdentityHashSet();
+  private MainDexClasses() {
+    this(Sets.newIdentityHashSet());
+  }
 
-    private Builder(AppInfo appInfo) {
-      this.appInfo = appInfo;
-    }
+  private MainDexClasses(Set<DexType> mainDexClasses) {
+    this.mainDexClasses = mainDexClasses;
+  }
 
-    public Builder addRoot(DexType type) {
-      assert isProgramClass(type) : type.toSourceString();
-      roots.add(type);
-      return this;
-    }
+  public static Builder builder() {
+    return new Builder();
+  }
 
-    public Builder addRoots(Collection<DexType> rootSet) {
-      assert rootSet.stream().allMatch(this::isProgramClass);
-      this.roots.addAll(rootSet);
-      return this;
-    }
+  public static MainDexClasses createEmptyMainDexClasses() {
+    return new MainDexClasses();
+  }
 
-    public Builder addDependency(DexType type) {
-      assert isProgramClass(type);
-      dependencies.add(type);
-      return this;
-    }
+  public void add(DexProgramClass clazz) {
+    mainDexClasses.add(clazz.getType());
+  }
 
-    public boolean contains(DexType type) {
-      return roots.contains(type) || dependencies.contains(type);
-    }
+  public void addAll(MainDexClasses other) {
+    mainDexClasses.addAll(other.mainDexClasses);
+  }
 
-    public MainDexClasses build() {
-      return new MainDexClasses(roots, dependencies);
-    }
+  public void addAll(MainDexTracingResult other) {
+    mainDexClasses.addAll(other.getClasses());
+  }
 
-    private boolean isProgramClass(DexType dexType) {
-      DexClass clazz = appInfo.definitionFor(dexType);
-      return clazz != null && clazz.isProgramClass();
+  public void addAll(Iterable<DexProgramClass> classes) {
+    for (DexProgramClass clazz : classes) {
+      add(clazz);
     }
   }
 
-  // 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;
+  public boolean contains(DexProgramClass clazz) {
+    return mainDexClasses.contains(clazz.getType());
+  }
 
-  private MainDexClasses(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 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() {
-    assert !roots.isEmpty() || dependencies.isEmpty();
-    return roots.isEmpty();
+    return mainDexClasses.isEmpty();
   }
 
-  public Set<DexType> getRoots() {
-    return roots;
+  public MainDexClasses rewrittenWithLens(GraphLens lens) {
+    MainDexClasses rewrittenMainDexClasses = createEmptyMainDexClasses();
+    for (DexType mainDexClass : mainDexClasses) {
+      DexType rewrittenMainDexClass = lens.lookupType(mainDexClass);
+      rewrittenMainDexClasses.mainDexClasses.add(rewrittenMainDexClass);
+    }
+    return rewrittenMainDexClasses;
   }
 
-  public Set<DexType> getDependencies() {
-    return dependencies;
+  public int size() {
+    return mainDexClasses.size();
   }
 
-  public Set<DexType> getClasses() {
-    return classes;
-  }
+  public static class Builder {
 
-  private void collectTypesMatching(
-      Set<DexType> types, Predicate<DexType> predicate, Consumer<DexType> consumer) {
-    types.forEach(
-        type -> {
-          if (predicate.test(type)) {
-            consumer.accept(type);
-          }
-        });
-  }
+    private final Set<DexType> mainDexClasses = Sets.newIdentityHashSet();
 
-  public MainDexClasses 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();
-  }
+    private Builder() {}
 
-  public static Builder builder(AppInfo appInfo) {
-    return new Builder(appInfo);
+    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/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index c122cd7..4fa5d3a 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -30,7 +30,7 @@
   private final Set<DexType> roots;
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Map<DexType, Boolean> annotationTypeContainEnum;
-  private final MainDexClasses.Builder mainDexClassesBuilder;
+  private final MainDexTracingResult.Builder mainDexClassesBuilder;
 
   public static void checkForAssumedLibraryTypes(AppInfo appInfo) {
     DexClass enumType = appInfo.definitionFor(appInfo.dexItemFactory().enumType);
@@ -54,7 +54,7 @@
     this.appView = appView;
     // Only consider program classes for the root set.
     this.roots = SetUtils.mapIdentityHashSet(roots, DexProgramClass::getType);
-    mainDexClassesBuilder = MainDexClasses.builder(appView.appInfo()).addRoots(this.roots);
+    mainDexClassesBuilder = MainDexTracingResult.builder(appView.appInfo()).addRoots(this.roots);
     annotationTypeContainEnum = new IdentityHashMap<>();
   }
 
@@ -62,7 +62,7 @@
     return appView.appInfo();
   }
 
-  public MainDexClasses run() {
+  public MainDexTracingResult run() {
     traceMainDexDirectDependencies();
     traceRuntimeAnnotationsWithEnumForMainDex();
     return mainDexClassesBuilder.build();
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
new file mode 100644
index 0000000..d333211
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
@@ -0,0 +1,116 @@
+// 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.DexType;
+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 = Sets.newIdentityHashSet();
+    public final Set<DexType> dependencies = Sets.newIdentityHashSet();
+
+    private Builder(AppInfo appInfo) {
+      this.appInfo = appInfo;
+    }
+
+    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 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;
+  }
+
+  private void collectTypesMatching(
+      Set<DexType> types, Predicate<DexType> predicate, Consumer<DexType> consumer) {
+    types.forEach(
+        type -> {
+          if (predicate.test(type)) {
+            consumer.accept(type);
+          }
+        });
+  }
+
+  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);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 1b51a73..6b5604e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -51,6 +51,9 @@
   private final Reporter reporter;
   private final boolean allowTestOptions;
 
+  public static final String FLATTEN_PACKAGE_HIERARCHY = "flattenpackagehierarchy";
+  public static final String REPACKAGE_CLASSES = "repackageclasses";
+
   private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
       "protomapping",
       "target",
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 0d803ea..3725f5f 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -187,7 +187,7 @@
   }
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final MainDexClasses mainDexClasses;
+  private final MainDexTracingResult mainDexClasses;
 
   /** The equivalence that should be used for the member buckets in {@link Representative}. */
   private final Equivalence<DexField> fieldEquivalence;
@@ -203,7 +203,7 @@
   public StaticClassMerger(
       AppView<AppInfoWithLiveness> appView,
       InternalOptions options,
-      MainDexClasses mainDexClasses) {
+      MainDexTracingResult mainDexClasses) {
     this.appView = appView;
     if (options.getProguardConfiguration().isOverloadAggressively()) {
       fieldEquivalence = FieldSignatureEquivalence.getEquivalenceIgnoreName();
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 17cf952..2d21927 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -217,14 +217,14 @@
   // All the bridge methods that have been synthesized during vertical class merging.
   private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
 
-  private final MainDexClasses mainDexClasses;
+  private final MainDexTracingResult mainDexClasses;
 
   public VerticalClassMerger(
       DexApplication application,
       AppView<AppInfoWithLiveness> appView,
       ExecutorService executorService,
       Timing timing,
-      MainDexClasses mainDexClasses) {
+      MainDexTracingResult mainDexClasses) {
     this.application = application;
     this.appInfo = appView.appInfo();
     this.appView = appView;
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 7f54137..a9d5c3d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -491,6 +491,10 @@
   private final boolean enableTreeShaking;
   private final boolean enableMinification;
 
+  public boolean isRelease() {
+    return !debug;
+  }
+
   public boolean isShrinking() {
     assert proguardConfiguration == null
         || enableTreeShaking == proguardConfiguration.isShrinking();
diff --git a/src/main/java/com/android/tools/r8/utils/MainDexList.java b/src/main/java/com/android/tools/r8/utils/MainDexListParser.java
similarity index 93%
rename from src/main/java/com/android/tools/r8/utils/MainDexList.java
rename to src/main/java/com/android/tools/r8/utils/MainDexListParser.java
index e323566..136ae2a 100644
--- a/src/main/java/com/android/tools/r8/utils/MainDexList.java
+++ b/src/main/java/com/android/tools/r8/utils/MainDexListParser.java
@@ -15,7 +15,7 @@
 import com.google.common.collect.Sets;
 import java.util.Set;
 
-public class MainDexList {
+public class MainDexListParser {
 
   public static DexType parseEntry(String clazz, DexItemFactory itemFactory) {
     if (!clazz.endsWith(CLASS_EXTENSION)) {
@@ -47,7 +47,10 @@
         try {
           result.add(parseEntry(line, itemFactory));
         } catch (CompilationError e) {
-          throw new CompilationError(e.getMessage(), e, resource.getOrigin(),
+          throw new CompilationError(
+              e.getMessage(),
+              e,
+              resource.getOrigin(),
               new TextPosition(offset, lineNumber, TextPosition.UNKNOWN_COLUMN));
         }
       }
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index dd5b43a..68fa8f3 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -82,7 +82,7 @@
 
   private static DexProgramClass mergeClasses(
       Reporter reporter, DexProgramClass a, DexProgramClass b) {
-    if (a.type.isD8R8SynthesizedClassType()) {
+    if (a.type.isLegacySynthesizedTypeAllowedDuplication()) {
       assert assertEqualClasses(a, b);
       return a;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index e9bf6cd..2e74be4 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -76,6 +76,10 @@
     return !workingList.isEmpty();
   }
 
+  public boolean isEmpty() {
+    return !hasNext();
+  }
+
   public void markAsSeen(T item) {
     seen.add(item);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ImmutableInt2ReferenceSortedMap.java b/src/main/java/com/android/tools/r8/utils/collections/ImmutableInt2ReferenceSortedMap.java
new file mode 100644
index 0000000..af41bf1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ImmutableInt2ReferenceSortedMap.java
@@ -0,0 +1,190 @@
+// 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.collections;
+
+import com.android.tools.r8.errors.Unreachable;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMaps;
+import it.unimi.dsi.fastutil.ints.IntSortedSet;
+import it.unimi.dsi.fastutil.objects.ObjectSortedSet;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import org.jetbrains.annotations.Nullable;
+
+public class ImmutableInt2ReferenceSortedMap<V> extends Int2ReferenceSortedMaps.EmptySortedMap<V> {
+
+  private final Int2ReferenceSortedMap<V> sortedMap;
+
+  private ImmutableInt2ReferenceSortedMap(Int2ReferenceSortedMap<V> sortedMap) {
+    this.sortedMap = sortedMap;
+  }
+
+  public static <V> ImmutableInt2ReferenceSortedMap<V> of(Int2ReferenceSortedMap<V> sortedMap) {
+    return new ImmutableInt2ReferenceSortedMap<>(sortedMap);
+  }
+
+  public static <V> ImmutableInt2ReferenceSortedMap<V> of(int[] keys, V[] values) {
+    return new ImmutableInt2ReferenceSortedMap<>(new Int2ReferenceAVLTreeMap<>(keys, values));
+  }
+
+  public static <V> Builder<V> builder() {
+    return new Builder<>();
+  }
+
+  public static class Builder<V> {
+
+    private final Int2ReferenceSortedMap<V> sortedMap = new Int2ReferenceAVLTreeMap<>();
+
+    public Builder<V> put(int k, V v) {
+      sortedMap.put(k, v);
+      return this;
+    }
+
+    public ImmutableInt2ReferenceSortedMap<V> build() {
+      return new ImmutableInt2ReferenceSortedMap<>(sortedMap);
+    }
+  }
+
+  @Override
+  public int size() {
+    return sortedMap.size();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public ObjectSortedSet<Entry<V>> int2ReferenceEntrySet() {
+    return sortedMap.int2ReferenceEntrySet();
+  }
+
+  @Deprecated
+  @Override
+  @SuppressWarnings("unchecked")
+  public ObjectSortedSet<Map.Entry<Integer, V>> entrySet() {
+    return sortedMap.entrySet();
+  }
+
+  @Override
+  public IntSortedSet keySet() {
+    return sortedMap.keySet();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Int2ReferenceSortedMap<V> subMap(final int from, final int to) {
+    return sortedMap.subMap(from, to);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Int2ReferenceSortedMap<V> headMap(final int to) {
+    return sortedMap.headMap(to);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Int2ReferenceSortedMap<V> tailMap(final int from) {
+    return sortedMap.tailMap(from);
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return sortedMap.isEmpty();
+  }
+
+  @Override
+  public int firstIntKey() {
+    return sortedMap.firstIntKey();
+  }
+
+  @Override
+  public int lastIntKey() {
+    return sortedMap.lastIntKey();
+  }
+
+  @Deprecated
+  @Override
+  public Int2ReferenceSortedMap<V> headMap(Integer oto) {
+    return sortedMap.headMap(oto);
+  }
+
+  @Deprecated
+  @Override
+  public Int2ReferenceSortedMap<V> tailMap(Integer ofrom) {
+    return sortedMap.tailMap(ofrom);
+  }
+
+  @Deprecated
+  @Override
+  public Int2ReferenceSortedMap<V> subMap(Integer ofrom, Integer oto) {
+    return sortedMap.subMap(ofrom, oto);
+  }
+
+  @Deprecated
+  @Override
+  public Integer firstKey() {
+    return sortedMap.firstKey();
+  }
+
+  @Deprecated
+  @Override
+  public Integer lastKey() {
+    return sortedMap.lastKey();
+  }
+
+  @Override
+  public V put(int key, V value) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public V put(Integer ok, V ov) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public void putAll(Map<? extends Integer, ? extends V> m) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Nullable
+  @Override
+  public V putIfAbsent(Integer key, V value) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public V compute(
+      Integer key, BiFunction<? super Integer, ? super V, ? extends V> remappingFunction) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public V computeIfAbsent(Integer key, Function<? super Integer, ? extends V> mappingFunction) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public V computeIfPresent(
+      Integer key, BiFunction<? super Integer, ? super V, ? extends V> remappingFunction) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public V remove(int key) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public boolean remove(Object key, Object value) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+
+  @Override
+  public V remove(Object ok) {
+    throw new Unreachable("Should not modify an immutable structure");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
index 92d51ea..0732bcb 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -15,6 +15,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 
@@ -50,6 +51,10 @@
     return new ProgramMethodSet(new LinkedHashMap<>());
   }
 
+  public static ProgramMethodSet createSorted() {
+    return new ProgramMethodSet(new TreeMap<>(DexMethod::slowCompareTo));
+  }
+
   public static ProgramMethodSet empty() {
     return EMPTY;
   }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 9aa9789..342920c 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -44,6 +44,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.ProguardClassFilter;
 import com.android.tools.r8.shaking.ProguardClassNameList;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -650,7 +651,8 @@
   protected static AppInfoWithClassHierarchy computeAppInfoWithClassHierarchy(AndroidApp app)
       throws Exception {
     return AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
-        readApplicationForDexOutput(app, new InternalOptions()));
+        readApplicationForDexOutput(app, new InternalOptions()),
+        MainDexClasses.createEmptyMainDexClasses());
   }
 
   protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithSubtyping(AndroidApp app)
@@ -1601,6 +1603,10 @@
     return AndroidApiLevel.O;
   }
 
+  public static AndroidApiLevel apiLevelWithNativeMultiDexSupport() {
+    return AndroidApiLevel.L;
+  }
+
   public Path compileToZip(
       TestParameters parameters, Collection<Class<?>> classPath, Class<?>... compilationUnit)
       throws Exception {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
index 226af98..b2e1d59 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -5,14 +5,21 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.readsInstanceField;
+import static com.android.tools.r8.utils.codeinspector.Matchers.writesInstanceField;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.A;
 import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.B;
 import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.Main;
+import com.android.tools.r8.horizontalclassmerging.ClassMerger;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 
 public class MergedConstructorForwardingTest extends HorizontalClassMergingTestBase {
@@ -29,15 +36,37 @@
         .addKeepMainRule(Main.class)
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("42", "13", "21", "39")
+        .assertSuccessWithOutputLines("42", "13", "21", "39", "print a", "print b")
         .inspect(
             codeInspector -> {
               if (enableHorizontalClassMerging) {
-                assertThat(codeInspector.clazz(A.class), isPresent());
+                ClassSubject aClassSubject = codeInspector.clazz(A.class);
+                assertThat(aClassSubject, isPresent());
+                FieldSubject classIdFieldSubject =
+                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+                assertThat(classIdFieldSubject, isPresent());
+
+                MethodSubject firstInitSubject = aClassSubject.init("int");
+                assertThat(firstInitSubject, isPresent());
+                assertThat(
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                MethodSubject otherInitSubject = aClassSubject.init("long", "int");
+                assertThat(otherInitSubject, isPresent());
+                assertThat(
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                MethodSubject printSubject = aClassSubject.method("void", "print");
+                assertThat(printSubject, isPresent());
+                assertThat(
+                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
+
                 // TODO(b/165517236): Explicitly check classes have been merged.
               } else {
                 assertThat(codeInspector.clazz(A.class), isPresent());
@@ -55,6 +84,11 @@
     public A(long x) {
       System.out.println(x);
     }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print a");
+    }
   }
 
   @NeverClassInline
@@ -66,6 +100,11 @@
     public B(long y) {
       System.out.println(y * 3);
     }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print b");
+    }
   }
 
   public static class Main {
@@ -74,6 +113,8 @@
       a = new A(13);
       B b = new B();
       b = new B(13);
+      a.print();
+      b.print();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
new file mode 100644
index 0000000..1f0daeb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MergedConstructorStackTraceTest extends HorizontalClassMergingTestBase {
+
+  public StackTrace expectedStackTrace;
+
+  public MergedConstructorStackTraceTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Main.class)
+            .assertFailure()
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              if (enableHorizontalClassMerging) {
+                StackTrace expectedStackTraceWithMergedConstructor =
+                    StackTrace.builder()
+                        .add(expectedStackTrace)
+                        .add(
+                            2,
+                            StackTraceLine.builder()
+                                .setClassName(A.class.getTypeName())
+                                .setMethodName("<init>")
+                                .setFileName(getClass().getSimpleName() + ".java")
+                                .setLineNumber(0)
+                                .build())
+                        .build();
+                assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+              } else {
+                assertThat(stackTrace, isSame(expectedStackTrace));
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverMerge
+  static class Parent {
+    Parent() {
+      if (System.currentTimeMillis() >= 0) {
+        throw new RuntimeException();
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class A extends Parent {
+    A() {}
+  }
+
+  @NeverClassInline
+  static class B extends Parent {
+    B() {}
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new A();
+      new B();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
new file mode 100644
index 0000000..119913b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class SuperConstructorCallsVirtualMethodTest extends HorizontalClassMergingTestBase {
+  public SuperConstructorCallsVirtualMethodTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("5", "foo hello", "B", "bar world", "5", "B")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                assertThat(codeInspector.clazz(Parent.class), isPresent());
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(Parent.class), isPresent());
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class Parent {
+    String field;
+
+    public Parent(String v) {
+      this.field = v;
+      print();
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println(field);
+    }
+  }
+
+  @NeverClassInline
+  public static class A extends Parent {
+    public A(String arg) {
+      super(arg);
+      System.out.println("foo " + arg);
+    }
+
+    @NeverInline
+    @Override
+    public void print() {
+      System.out.println(5);
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends Parent {
+    public B(String arg) {
+      super("B");
+      System.out.println("bar " + arg);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A("hello");
+      B b = new B("world");
+      a.print();
+      b.print();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodOverrideParentCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodOverrideParentCollisionTest.java
new file mode 100644
index 0000000..e304a70
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodOverrideParentCollisionTest.java
@@ -0,0 +1,92 @@
+// 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.horizontalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
+import org.junit.Test;
+
+public class VirtualMethodOverrideParentCollisionTest extends HorizontalClassMergingTestBase {
+
+  public VirtualMethodOverrideParentCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(this.getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo", "bar", "foo", "parent")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class Parent {
+    @NeverInline
+    public void foo() {
+      System.out.println("parent");
+    }
+  }
+
+  @NeverClassInline
+  public static class A extends Parent {
+    @NeverInline
+    @Override
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends Parent {
+    // TODO(b/164924717): remove non overlapping constructor requirement
+    public B(String s) {}
+
+    @NeverInline
+    public void bar() {
+      System.out.println("bar");
+    }
+  }
+
+  public static class Main {
+    @NeverInline
+    static void callFoo(Parent p) {
+      p.foo();
+    }
+
+    public static void main(String[] args) {
+      A a = new A();
+      a.foo();
+      B b = new B("");
+      b.bar();
+      callFoo(a);
+      callFoo(b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java
new file mode 100644
index 0000000..f323ec7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMerged.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class VirtualMethodSuperMerged extends HorizontalClassMergingTestBase {
+  public VirtualMethodSuperMerged(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(this.getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableMergeAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo", "parent b", "parent b", "x", "parent b")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                assertThat(codeInspector.clazz(ParentA.class), isPresent());
+                assertThat(codeInspector.clazz(ParentB.class), not(isPresent()));
+                assertThat(codeInspector.clazz(X.class), isPresent());
+                assertThat(codeInspector.clazz(Y.class), not(isPresent()));
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(ParentA.class), isPresent());
+                assertThat(codeInspector.clazz(ParentB.class), isPresent());
+                assertThat(codeInspector.clazz(X.class), isPresent());
+                assertThat(codeInspector.clazz(Y.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class ParentA {
+    @NeverInline
+    void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  public static class ParentB {
+    @NeverInline
+    void print() {
+      System.out.println("parent b");
+    }
+  }
+
+  @NeverClassInline
+  public static class X extends ParentB {
+    public X() {
+      print();
+    }
+
+    @NeverInline
+    @Override
+    void print() {
+      super.print();
+      System.out.println("x");
+    }
+  }
+
+  @NeverClassInline
+  public static class Y extends ParentB {
+    public Y() {
+      print();
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      ParentA a = new ParentA();
+      a.foo();
+      ParentB b = new ParentB();
+      b.print();
+      X x = new X();
+      Y y = new Y();
+    }
+  }
+}
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 db4d3df..fa6e974 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
@@ -171,7 +171,7 @@
       ToolHelper.runL8(l8Builder.build());
 
       // Run on the JVM with desuagred library on classpath.
-      TestRunResult result =
+      TestRunResult<?> result =
           testForJvm()
               .addProgramFiles(jar)
               .addRunClasspathFiles(desugaredLib)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
index 2b84919..0245f6e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
@@ -4,35 +4,82 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexFileMergerHelper;
 import com.android.tools.r8.L8;
 import com.android.tools.r8.L8Command;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11DesugaredLibraryTestBase;
+import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableMap;
 import java.nio.file.Path;
 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 MergingJ$Test extends Jdk11DesugaredLibraryTestBase {
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public MergingJ$Test(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
   @Test
   public void testMergingJ$() throws Exception {
     Path mergerInputPart1 = buildSplitDesugaredLibraryPart1();
     Path mergerInputPart2 = buildSplitDesugaredLibraryPart2();
-    CodeInspector codeInspectorOutput = null;
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8()
+                .addProgramFiles(mergerInputPart1, mergerInputPart2)
+                .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics
+                          .assertOnlyErrors()
+                          .assertErrorsMatch(diagnosticType(DuplicateTypesDiagnostic.class));
+                    }));
+  }
+
+  @Test
+  public void testMergingJ$WithDexFileMergerHelper() throws Exception {
+    Path mergerInputPart1 = buildSplitDesugaredLibraryPart1();
+    Path mergerInputPart2 = buildSplitDesugaredLibraryPart2();
+    Path merged = temp.newFolder().toPath().resolve("merged.jar");
+    D8Command command =
+        D8Command.builder()
+            .addProgramFiles(mergerInputPart1, mergerInputPart2)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .setOutput(merged, OutputMode.DexIndexed)
+            .build();
     try {
-      codeInspectorOutput =
-          testForD8()
-              .addProgramFiles(mergerInputPart1, mergerInputPart2)
-              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-              .compile()
-              .inspector();
+      DexFileMergerHelper.run(
+          command,
+          true,
+          ImmutableMap.<String, Integer>builder()
+              .put(mergerInputPart1.toString(), 1)
+              .put(mergerInputPart2.toString(), 2)
+              .build());
     } catch (Exception e) {
       if (e.getCause().getMessage().contains("Merging dex file containing classes with prefix")) {
         // TODO(b/138278440): Forbid to merge j$ classes in a Google3 compliant way.
@@ -44,6 +91,7 @@
       }
       throw e;
     }
+    CodeInspector codeInspectorOutput = new CodeInspector(merged);
     CodeInspector codeInspectorSplit1 = new CodeInspector(mergerInputPart1);
     CodeInspector codeInspectorSplit2 = new CodeInspector(mergerInputPart2);
     assertNotNull(codeInspectorOutput);
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index eeedbab..b48e0a93 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.naming.NamingLens;
@@ -21,8 +23,10 @@
 
   private ObjectToOffsetMapping emptyObjectTObjectMapping() {
     return new ObjectToOffsetMapping(
-        DexApplication.builder(new InternalOptions(new DexItemFactory(), new Reporter()), null)
-            .build(),
+        AppInfo.createInitialAppInfo(
+            DexApplication.builder(new InternalOptions(new DexItemFactory(), new Reporter()), null)
+                .build()),
+        GraphLens.getIdentityLens(),
         NamingLens.getIdentityLens(),
         InitClassLens.getDefault(),
         Collections.emptyList(),
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 5cb94de..b567056 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
@@ -144,8 +145,9 @@
     DexProgramClass sharedSynthesizedClass =
         makeClass(options, "SharedSynthesized", 100, Constants.MAX_NON_JUMBO_INDEX - 1, classes);
 
-    DexApplication.Builder builder = DirectMappedDexApplication.builder(options, Timing.empty());
-    builder.addSynthesizedClass(sharedSynthesizedClass, false);
+    LazyLoadedDexApplication.Builder builder =
+        DirectMappedDexApplication.builder(options, Timing.empty());
+    builder.addSynthesizedClass(sharedSynthesizedClass);
     classes.forEach(builder::addProgramClass);
     DexApplication application = builder.build();
 
@@ -153,9 +155,7 @@
     options.programConsumer = consumer;
     ApplicationWriter writer =
         new ApplicationWriter(
-            application,
             AppView.createForD8(AppInfo.createInitialAppInfo(application)),
-            options,
             null,
             GraphLens.getIdentityLens(),
             InitClassLens.getDefault(),
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index f503359..0ef3bdf 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -369,7 +369,7 @@
 
     // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
     // shaken away.
-    CodeInspector inspector = new CodeInspector(base, proguardMap.toString());
+    CodeInspector inspector = new CodeInspector(base, proguardMap);
     ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
     assertTrue(subject.isPresent());
     assertTrue(subject.isRenamed());
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
new file mode 100644
index 0000000..1ab86b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+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 StringValueOfEnumUnboxingTest extends EnumUnboxingTestBase {
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public StringValueOfEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> classToTest = Main.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(classToTest, MyEnum.class)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(enumKeepRules.getKeepRules())
+            .enableNeverClassInliningAnnotations()
+            .enableInliningAnnotations()
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m))
+            .run(parameters.getRuntime(), classToTest)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B,
+    C
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.ordinal());
+      System.out.println(0);
+      System.out.println(getString(MyEnum.A));
+      System.out.println("A");
+      System.out.println(getString(null));
+      System.out.println("null");
+    }
+
+    @NeverInline
+    private static String getString(MyEnum e) {
+      return String.valueOf(e);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/D8PhotosVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8PhotosVerificationTest.java
deleted file mode 100644
index 4094bb7..0000000
--- a/src/test/java/com/android/tools/r8/internal/D8PhotosVerificationTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2017, 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.internal;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
-import org.junit.Test;
-
-public class D8PhotosVerificationTest extends CompilationTestBase {
-  public static final String PHOTOS =
-      "third_party/photos/2017-06-06/PhotosEnglishOnlyLegacy_proguard.jar";
-
-  public void runD8AndCheckVerification(CompilationMode mode, String version)
-      throws ExecutionException, IOException, CompilationFailedException {
-    runAndCheckVerification(CompilerUnderTest.D8, mode, version, null, version);
-  }
-
-  @Test
-  public void verify() throws Exception {
-    runD8AndCheckVerification(CompilationMode.RELEASE, PHOTOS);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
index 97b4db0..bbddb47 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -69,8 +69,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
index b2498b5..0a304b1 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
@@ -78,8 +78,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
index b48bec3..79e7eb1 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
@@ -78,8 +78,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
index 632210e..09293ac 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
@@ -78,8 +78,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
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 b351ca2..1d7686c 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,7 @@
 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.MainDexClasses;
+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 +113,7 @@
 
     public String run() throws IOException {
       Timing timing = Timing.empty();
-      IRConverter converter = new IRConverter(appView, timing, null, MainDexClasses.NONE);
+      IRConverter converter = new IRConverter(appView, timing, null, MainDexTracingResult.NONE);
       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/optimize/typechecks/InstanceOfMethodSpecializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java
new file mode 100644
index 0000000..5d13f66
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/typechecks/InstanceOfMethodSpecializationTest.java
@@ -0,0 +1,181 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.typechecks;
+
+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.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InstanceOfMethodSpecializationTest extends TestBase {
+
+  private static final List<String> EXPECTED =
+      ImmutableList.of(
+          "true", "false", "false", "true", "false", "true", "true", "false", "false", "true",
+          "true", "false", "true", "false", "true");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InstanceOfMethodSpecializationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InstanceOfMethodSpecializationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("isA"), not(isPresent()));
+    assertThat(aClassSubject.uniqueMethodWithName("isB"), not(isPresent()));
+    assertThat(aClassSubject.uniqueMethodWithName("isC"), not(isPresent()));
+    assertThat(aClassSubject.uniqueMethodWithName("isSuper"), isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("isSub"), isPresent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    assertThat(bClassSubject.uniqueMethodWithName("isA"), not(isPresent()));
+    assertThat(bClassSubject.uniqueMethodWithName("isB"), not(isPresent()));
+    assertThat(bClassSubject.uniqueMethodWithName("isC"), not(isPresent()));
+    assertThat(bClassSubject.uniqueMethodWithName("isSuper"), not(isPresent()));
+    assertThat(bClassSubject.uniqueMethodWithName("isSub"), not(isPresent()));
+
+    ClassSubject cClassSubject = inspector.clazz(C.class);
+    assertThat(cClassSubject, isPresent());
+    assertThat(cClassSubject.uniqueMethodWithName("isA"), not(isPresent()));
+    assertThat(cClassSubject.uniqueMethodWithName("isB"), not(isPresent()));
+    assertThat(cClassSubject.uniqueMethodWithName("isC"), not(isPresent()));
+    assertThat(cClassSubject.uniqueMethodWithName("isSuper"), isPresent());
+    assertThat(cClassSubject.uniqueMethodWithName("isSub"), isPresent());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      A a = System.currentTimeMillis() > 0 ? new A() : new B();
+      A b = System.currentTimeMillis() > 0 ? new B() : new A();
+      A c = System.currentTimeMillis() > 0 ? new C() : new A();
+      System.out.println(a.isA());
+      System.out.println(a.isB());
+      System.out.println(a.isC());
+      System.out.println(a.isSuper());
+      System.out.println(a.isSub());
+      System.out.println(b.isA());
+      System.out.println(b.isB());
+      System.out.println(b.isC());
+      System.out.println(b.isSuper());
+      System.out.println(b.isSub());
+      System.out.println(c.isA());
+      System.out.println(c.isB());
+      System.out.println(c.isC());
+      System.out.println(c.isSuper());
+      System.out.println(c.isSub());
+    }
+  }
+
+  public static class A {
+
+    boolean isA() {
+      return true;
+    }
+
+    boolean isB() {
+      return false;
+    }
+
+    boolean isC() {
+      return false;
+    }
+
+    boolean isSuper() {
+      return true;
+    }
+
+    boolean isSub() {
+      return false;
+    }
+  }
+
+  public static class B extends A {
+
+    @Override
+    boolean isA() {
+      return true;
+    }
+
+    @Override
+    boolean isB() {
+      return true;
+    }
+
+    @Override
+    boolean isSuper() {
+      return false;
+    }
+
+    @Override
+    boolean isSub() {
+      return true;
+    }
+  }
+
+  public static class C extends A {
+
+    @Override
+    boolean isA() {
+      return true;
+    }
+
+    @Override
+    boolean isC() {
+      return true;
+    }
+
+    @Override
+    boolean isSuper() {
+      return false;
+    }
+
+    @Override
+    boolean isSub() {
+      return true;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 08a4ae9..0d96a65 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -162,10 +162,10 @@
         annotationElement[0].value.asDexValueString().value.toSourceString());
   }
 
-  private String getGeneratedProguardMap() throws IOException {
+  private Path getGeneratedProguardMap() throws IOException {
     Path mapFile = Paths.get(tmpOutputDir.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME);
     if (Files.exists(mapFile)) {
-      return mapFile.toAbsolutePath().toString();
+      return mapFile.toAbsolutePath();
     }
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index fdc2ab9..37d2899 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -71,7 +71,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.MainDexList;
+import com.android.tools.r8.utils.MainDexListParser;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -182,7 +182,7 @@
   }
 
   private static Set<DexType> parse(Path path, DexItemFactory itemFactory) throws IOException {
-    return MainDexList.parseList(StringResource.fromFile(path), itemFactory);
+    return MainDexListParser.parseList(StringResource.fromFile(path), itemFactory);
   }
 
   @Rule
@@ -331,9 +331,11 @@
   @Test
   public void singleEntryNoNewLine() throws Exception {
     DexItemFactory factory = new DexItemFactory();
-    Set<DexType> types = MainDexList.parseList(
-        StringResource.fromString("desugaringwithmissingclasstest1/Main.class", Origin.unknown()),
-        factory);
+    Set<DexType> types =
+        MainDexListParser.parseList(
+            StringResource.fromString(
+                "desugaringwithmissingclasstest1/Main.class", Origin.unknown()),
+            factory);
     assertEquals(1, types.size());
     assertEquals(
         "Ldesugaringwithmissingclasstest1/Main;",
@@ -350,7 +352,7 @@
     for (String entry : lines) {
       DexType type = factory.createType("L" + entry.replace(".class", "") + ";");
       assertTrue(types.contains(type));
-      assertSame(type, MainDexList.parseEntry(entry, factory));
+      assertSame(type, MainDexListParser.parseEntry(entry, factory));
     }
   }
 
@@ -866,9 +868,7 @@
     DirectMappedDexApplication application = builder.build().toDirect();
     ApplicationWriter writer =
         new ApplicationWriter(
-            application,
             AppView.createForD8(AppInfo.createInitialAppInfo(application)),
-            options,
             null,
             GraphLens.getIdentityLens(),
             InitClassLens.getDefault(),
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
index bf33b1c..4f5e54a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -10,17 +10,39 @@
 
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.hamcrest.CoreMatchers;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class MainDexWarningsTest extends TestBase {
 
-  private List<Class<?>> testClasses = ImmutableList.of(Main.class, Static.class, Static2.class);
-  private Class<?> mainClass = Main.class;
+  private static final List<Class<?>> testClasses =
+      ImmutableList.of(Main.class, Static.class, Static2.class);
+  private static final List<Class<?>> testClassesWithoutStatic =
+      ImmutableList.of(Main.class, Static2.class);
+  private static final Class<?> mainClass = Main.class;
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexWarningsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
 
   private void classStaticGone(CodeInspector inspector) {
     assertThat(inspector.clazz(Static.class), CoreMatchers.not(isPresent()));
@@ -29,12 +51,13 @@
 
   @Test
   public void testNoWarningFromMainDexRules() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .setMinApi(AndroidApiLevel.K)
         .addProgramClasses(testClasses)
         .addKeepMainRule(mainClass)
         // Include main dex rule for class Static.
         .addMainDexClassRules(Main.class, Static.class)
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::classStaticGone)
         .assertNoMessages();
@@ -42,13 +65,14 @@
 
   @Test
   public void testWarningFromManualMainDexList() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .setMinApi(AndroidApiLevel.K)
-        .addProgramClasses(testClasses)
+        .addProgramClasses(testClassesWithoutStatic)
         .addKeepMainRule(mainClass)
         // Include explicit main dex entry for class Static.
         .addMainDexListClasses(Static.class)
         .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::classStaticGone)
         .assertOnlyWarnings()
@@ -59,15 +83,16 @@
 
   @Test
   public void testWarningFromManualMainDexListWithRuleAsWell() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .setMinApi(AndroidApiLevel.K)
-        .addProgramClasses(testClasses)
+        .addProgramClasses(testClassesWithoutStatic)
         .addKeepMainRule(mainClass)
         // Include explicit main dex entry for class Static.
         .addMainDexListClasses(Main.class, Static.class)
         // Include main dex rule for class Static2.
         .addMainDexClassRules(Static2.class)
         .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::classStaticGone)
         .assertOnlyWarnings()
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 0c4f597..0da2866 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -33,11 +33,21 @@
 
     private Builder() {}
 
+    public Builder add(StackTrace stackTrace) {
+      stackTraceLines.addAll(stackTrace.getStackTraceLines());
+      return this;
+    }
+
     public Builder add(StackTraceLine line) {
       stackTraceLines.add(line);
       return this;
     }
 
+    public Builder add(int i, StackTraceLine line) {
+      stackTraceLines.add(i, line);
+      return this;
+    }
+
     public Builder addWithoutFileNameAndLineNumber(Class<?> clazz, String methodName) {
       return addWithoutFileNameAndLineNumber(clazz.getTypeName(), methodName);
     }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
index 47157aa..1639c79 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
@@ -47,7 +47,6 @@
 
   @Test
   public void testR8() throws Exception {
-    assumeTrue(parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addInnerClasses(RepackageAfterCollisionWithPackagePrivateSignatureTest.class)
         .addKeepClassAndMembersRules(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
index 69a88d0..c656981 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -109,7 +109,6 @@
 
   @Test
   public void testR8() throws Exception {
-    assumeTrue(!enableExperimentalRepackaging || parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addProgramFiles(ToolHelper.getClassFilesForTestPackage(TestClass.class.getPackage()))
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
new file mode 100644
index 0000000..00dd079
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+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 RepackageWithMainDexListTest extends TestBase {
+
+  private static final String REPACKAGE_DIR = "foo";
+
+  private final String flattenPackageHierarchyOrRepackageClasses;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters()
+            .withDexRuntimes()
+            .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+            .build());
+  }
+
+  public RepackageWithMainDexListTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        // -keep,allowobfuscation does not prohibit repackaging.
+        .addKeepClassRulesWithAllowObfuscation(TestClass.class, OtherTestClass.class)
+        .addKeepRules(
+            "-keepclassmembers class " + TestClass.class.getTypeName() + " { <methods>; }")
+        .addKeepRules(
+            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
+        // Add a class that will be repackaged to the main dex list.
+        .addMainDexListClasses(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              assertFalse(options.testing.enableExperimentalRepackaging);
+              options.testing.enableExperimentalRepackaging = true;
+            })
+        // Debug mode to enable minimal main dex.
+        .debug()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(this::checkCompileResult)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
+    Path out = temp.newFolder().toPath();
+    compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
+    Path classes = out.resolve("classes.dex");
+    Path classes2 = out.resolve("classes2.dex");
+    inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
+    inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
+  }
+
+  private void inspectMainDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(TestClass.class), isPresent());
+    assertThat(inspector.clazz(OtherTestClass.class), not(isPresent()));