Merge commit 'ecd7ced0dd6cdd020f111a7fac9c41e25866b96f' into dev-release
diff --git a/.gitignore b/.gitignore
index 26634e8..8534e4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,6 +80,8 @@
 third_party/gradle/gradle
 third_party/gradle/gradle.tar.gz
 third_party/internal/*
+third_party/internal-apps/youtube_15_33
+third_party/internal-apps/youtube_15_33.tar.gz
 third_party/iosched_2019
 third_party/iosched_2019.tar.gz
 third_party/jacoco/0.8.2/*
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 5960a22..a4e4210 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -145,7 +145,7 @@
       mixins: "normal"
       priority: 26
       recipe {
-        properties_j: "test_options:[\"--desugared-library\", \"HEAD\"]"
+        properties_j: "test_options:[\"--no_internal\", \"--desugared-library\", \"HEAD\"]"
       }
     }
     builders {
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 2cf08a6..0e7614f 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -175,6 +175,14 @@
     category: "win release"
     short_name: "win"
   }
+
+}
+consoles {
+  id: "kotlin"
+  name: "Kotlin"
+  repo_url: "https://github.com/google/kotlin"
+  refs: "regexp:refs/heads/.*"
+  manifest_name: "REVISION"
   builders {
     name: "buildbucket/luci.r8.ci/kotlin-builder"
     category: "kotlin"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 998b838..cdbcdd8 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.RecordRewritingNamingLens;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
@@ -184,14 +185,6 @@
       AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
       SyntheticItems.collectSyntheticInputs(appView);
 
-      if (!options.mainDexKeepRules.isEmpty()) {
-        MainDexInfo mainDexInfo =
-            new GenerateMainDexList(options)
-                .traceMainDex(
-                    executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
-        appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
-      }
-
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
       if (AssertionsRewriter.isEnabled(options)) {
@@ -267,26 +260,22 @@
       Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
 
       InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
+      NamingLens namingLens = NamingLens.getIdentityLens();
+      if (appView.rewritePrefix.isRewriting()) {
+        namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
+      }
+      if (appView.options().shouldDesugarRecords()) {
+        namingLens = RecordRewritingNamingLens.createRecordRewritingNamingLens(appView, namingLens);
+      }
       if (options.isGeneratingClassFiles()) {
         // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
         SyntheticFinalization.finalize(appView);
-        new CfApplicationWriter(
-                appView,
-                marker,
-                GraphLens.getIdentityLens(),
-                appView.rewritePrefix.isRewriting()
-                    ? PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView)
-                    : NamingLens.getIdentityLens(),
-                null)
+        new CfApplicationWriter(appView, marker, GraphLens.getIdentityLens(), namingLens, null)
             .write(options.getClassFileConsumer());
       } else {
-        NamingLens namingLens;
         if (!hasDexResources || !hasClassResources || !appView.rewritePrefix.isRewriting()) {
           // All inputs are either dex or cf, or there is nothing to rewrite.
-          namingLens =
-              hasDexResources
-                  ? NamingLens.getIdentityLens()
-                  : PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+          namingLens = hasDexResources ? NamingLens.getIdentityLens() : namingLens;
           new GenericSignatureRewriter(appView, namingLens)
               .run(appView.appInfo().classes(), executor);
           new KotlinMetadataRewriter(appView, namingLens).runForD8(executor);
@@ -298,7 +287,13 @@
           timing.begin("Rewrite non-dex inputs");
           DexApplication app =
               rewriteNonDexInputs(
-                  appView, inputApp, options, executor, timing, appView.appInfo().app());
+                  appView,
+                  inputApp,
+                  options,
+                  executor,
+                  timing,
+                  appView.appInfo().app(),
+                  namingLens);
           timing.end();
           appView.setAppInfo(
               new AppInfo(
@@ -307,6 +302,16 @@
           namingLens = NamingLens.getIdentityLens();
         }
 
+        // Since tracing is not lens aware, this needs to be done prior to synthetic finalization
+        // which will construct a graph lens.
+        if (!options.mainDexKeepRules.isEmpty()) {
+          MainDexInfo mainDexInfo =
+              new GenerateMainDexList(options)
+                  .traceMainDex(
+                      executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
+          appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
+        }
+
         // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
         SyntheticFinalization.finalize(appView);
 
@@ -337,7 +342,8 @@
       InternalOptions options,
       ExecutorService executor,
       Timing timing,
-      DexApplication app)
+      DexApplication app,
+      NamingLens desugaringLens)
       throws IOException, ExecutionException {
     // TODO(b/154575955): Remove the naming lens in D8.
     appView
@@ -363,17 +369,15 @@
             appView.appInfo().getSyntheticItems().commit(cfApp),
             appView.appInfo().getMainDexInfo()));
     ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
-    NamingLens prefixRewritingNamingLens =
-        PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
-    new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
+    new GenericSignatureRewriter(appView, desugaringLens)
         .run(appView.appInfo().classes(), executor);
-    new KotlinMetadataRewriter(appView, prefixRewritingNamingLens).runForD8(executor);
+    new KotlinMetadataRewriter(appView, desugaringLens).runForD8(executor);
     new ApplicationWriter(
             appView,
             null,
             GraphLens.getIdentityLens(),
             InitClassLens.getDefault(),
-            prefixRewritingNamingLens,
+            desugaringLens,
             null,
             convertedCfFiles)
         .write(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c5eac58..05c2e8e 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -515,6 +515,7 @@
     return builder
         .setIntermediate(intermediate)
         .setDesugaredLibraryConfiguration(libraryConfiguration)
+        .setMainDexKeepRules(mainDexKeepRules)
         .build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/DumpOptions.java b/src/main/java/com/android/tools/r8/DumpOptions.java
index cd2ac79..3feeee3 100644
--- a/src/main/java/com/android/tools/r8/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/DumpOptions.java
@@ -8,8 +8,10 @@
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.ThreadUtils;
+import java.util.List;
 import java.util.Optional;
 
 @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@@ -49,6 +51,7 @@
   private final DesugaredLibraryConfiguration desugaredLibraryConfiguration;
   private final FeatureSplitConfiguration featureSplitConfiguration;
   private final ProguardConfiguration proguardConfiguration;
+  private final List<ProguardConfigurationRule> mainDexKeepRules;
 
   // Reporting only.
   private final boolean dumpInputToFile;
@@ -68,6 +71,7 @@
       Optional<Boolean> forceProguardCompatibility,
       FeatureSplitConfiguration featureSplitConfiguration,
       ProguardConfiguration proguardConfiguration,
+      List<ProguardConfigurationRule> mainDexKeepRules,
       boolean dumpInputToFile) {
     this.tool = tool;
     this.compilationMode = compilationMode;
@@ -83,6 +87,7 @@
     this.forceProguardCompatibility = forceProguardCompatibility;
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.proguardConfiguration = proguardConfiguration;
+    this.mainDexKeepRules = mainDexKeepRules;
     this.dumpInputToFile = dumpInputToFile;
   }
 
@@ -137,6 +142,14 @@
     return proguardConfiguration == null ? null : proguardConfiguration.getParsedConfiguration();
   }
 
+  public boolean hasMainDexKeepRules() {
+    return mainDexKeepRules != null;
+  }
+
+  public List<ProguardConfigurationRule> getMainDexKeepRules() {
+    return mainDexKeepRules;
+  }
+
   public boolean dumpInputToFile() {
     return dumpInputToFile;
   }
@@ -161,6 +174,7 @@
     private DesugaredLibraryConfiguration desugaredLibraryConfiguration;
     private FeatureSplitConfiguration featureSplitConfiguration;
     private ProguardConfiguration proguardConfiguration;
+    private List<ProguardConfigurationRule> mainDexKeepRules;
 
     // Reporting only.
     private boolean dumpInputToFile;
@@ -241,6 +255,11 @@
       return this;
     }
 
+    public Builder setMainDexKeepRules(List<ProguardConfigurationRule> mainDexKeepRules) {
+      this.mainDexKeepRules = mainDexKeepRules;
+      return this;
+    }
+
     public DumpOptions build() {
       return new DumpOptions(
           tool,
@@ -257,6 +276,7 @@
           forceProguardCompatibility,
           featureSplitConfiguration,
           proguardConfiguration,
+          mainDexKeepRules,
           dumpInputToFile);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 07783b3..2a794ab 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
+import com.android.tools.r8.StringConsumer.ForwardingConsumer;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -21,14 +22,13 @@
 import com.android.tools.r8.shaking.RootSetUtils.MainDexRootSet;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SortingStringConsumer;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -42,21 +42,13 @@
     this.options = options;
   }
 
-  private List<String> run(AndroidApp app, ExecutorService executor)
+  private void run(AndroidApp app, ExecutorService executor, SortingStringConsumer consumer)
       throws IOException {
     try {
-      // TODO(b/178231294): Clean up this such that we do not both return the result and call the
-      //  consumer.
       DexApplication application = new ApplicationReader(app, options, timing).read(executor);
-      List<String> result = new ArrayList<>();
       traceMainDex(executor, application, MainDexInfo.none())
-          .forEach(type -> result.add(type.toBinaryName() + ".class"));
-      Collections.sort(result);
-      if (options.mainDexListConsumer != null) {
-        options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
-        options.mainDexListConsumer.finished(options.reporter);
-      }
-      return result;
+          .forEach(type -> consumer.accept(type.toBinaryName() + ".class", options.reporter));
+      consumer.finished(options.reporter);
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
     }
@@ -137,11 +129,11 @@
   /**
    * Main API entry for computing the main-dex list.
    *
-   * The main-dex list is represented as a list of strings, each string specifies one class to
+   * <p>The main-dex list is represented as a list of strings, each string specifies one class to
    * keep in the primary dex file (<code>classes.dex</code>).
    *
-   * A class is specified using the following format: "com/example/MyClass.class". That is
-   * "/" as separator between package components, and a trailing ".class".
+   * <p>A class is specified using the following format: "com/example/MyClass.class". That is "/" as
+   * separator between package components, and a trailing ".class".
    *
    * @param command main dex-list generator command.
    * @param executor executor service from which to get threads for multi-threaded processing.
@@ -151,17 +143,28 @@
       throws CompilationFailedException {
     AndroidApp app = command.getInputApp();
     InternalOptions options = command.getInternalOptions();
-    Box<List<String>> result = new Box<>();
+    List<String> result = new ArrayList<>();
     ExceptionUtils.withMainDexListHandler(
         command.getReporter(),
         () -> {
           try {
-            result.set(new GenerateMainDexList(options).run(app, executor));
+            new GenerateMainDexList(options)
+                .run(
+                    app,
+                    executor,
+                    new SortingStringConsumer(
+                        new ForwardingConsumer(options.mainDexListConsumer) {
+                          @Override
+                          public void accept(String string, DiagnosticsHandler handler) {
+                            result.add(string);
+                            super.accept(string, handler);
+                          }
+                        }));
           } finally {
             executor.shutdown();
           }
         });
-    return result.get();
+    return result;
   }
 
   public static void main(String[] args) throws CompilationFailedException {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index b08a738..85bc305 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.JoiningStringConsumer;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
@@ -115,7 +116,7 @@
           factory,
           getAppBuilder().build(),
           mainDexKeepRules,
-          mainDexListConsumer,
+          new JoiningStringConsumer(mainDexListConsumer, "\n"),
           mainDexKeptGraphConsumer,
           getReporter());
     }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9158d3b..b77c8ed 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -495,16 +495,13 @@
             && options.isShrinking()) {
           timing.begin("HorizontalClassMerger");
           HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness);
-          DirectMappedDexApplication.Builder appBuilder =
-              appView.appInfo().app().asDirect().builder();
           HorizontalClassMergerResult horizontalClassMergerResult =
-              merger.run(appBuilder, runtimeTypeCheckInfo);
+              merger.run(runtimeTypeCheckInfo);
           if (horizontalClassMergerResult != null) {
             // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that
             // allocations sites, fields accesses, etc. are correctly transferred to the target
             // classes.
-            appView.rewriteWithLensAndApplication(
-                horizontalClassMergerResult.getGraphLens(), appBuilder.build());
+            appView.rewriteWithLens(horizontalClassMergerResult.getGraphLens());
             horizontalClassMergerResult
                 .getFieldAccessInfoCollectionModifier()
                 .modify(appViewWithLiveness);
@@ -513,7 +510,7 @@
                 PrunedItems.builder()
                     .setPrunedApp(appView.appInfo().app())
                     .addRemovedClasses(appView.horizontallyMergedClasses().getSources())
-                    .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getTargets())
+                    .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getSources())
                     .build());
           }
           timing.end();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d07b18..9663d3f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1002,6 +1002,7 @@
         .setForceProguardCompatibility(forceProguardCompatibility)
         .setFeatureSplitConfiguration(featureSplitConfiguration)
         .setProguardConfiguration(proguardConfiguration)
+        .setMainDexKeepRules(mainDexKeepRules)
         .setDesugaredLibraryConfiguration(libraryConfiguration)
         .build();
   }
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index b01f9a3..77abde6 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -240,7 +240,7 @@
       Stream<DexAnnotation> classAnnotations = classDef.annotations().stream();
       Stream<DexAnnotation> fieldAnnotations =
           Streams.stream(classDef.fields())
-              .filter(DexEncodedField::hasAnnotation)
+              .filter(DexEncodedField::hasAnnotations)
               .flatMap(f -> f.annotations().stream());
       Stream<DexAnnotation> methodAnnotations =
           Streams.stream(classDef.methods())
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 09f14ce..9862f27 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -59,6 +59,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Monitor;
@@ -396,10 +397,14 @@
     builder.append(callSite.methodName);
     builder.append(callSite.methodProto.toDescriptorString());
     if (callSite.bootstrapArgs.size() > 1) {
-      DexMethodHandle handle = callSite.bootstrapArgs.get(1).asDexValueMethodHandle().getValue();
-      builder.append(", handle:");
-      builder.append(handle.toSourceString());
-      builder.append(", itf: ").append(handle.isInterface);
+      DexValue.DexValueMethodHandle dexValueMethodHandle =
+          callSite.bootstrapArgs.get(1).asDexValueMethodHandle();
+      if (dexValueMethodHandle != null) {
+        DexMethodHandle handle = dexValueMethodHandle.getValue();
+        builder.append(", handle:");
+        builder.append(handle.toSourceString());
+        builder.append(", itf: ").append(handle.isInterface);
+      }
     }
     builder.append(", bsm:");
     appendMethod(bootstrapMethod.asMethod());
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 22421de..4fa3241 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
@@ -82,6 +82,6 @@
       InitClassLens initClassLens) {
     // ..., arrayref →
     // ..., length
-    frameBuilder.popAndDiscard(factory.objectArrayType).push(factory.intType);
+    frameBuilder.popAndDiscardInitialized(factory.objectArrayType).push(factory.intType);
   }
 }
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 c82e53e..4f851a0 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
@@ -128,7 +128,7 @@
       InitClassLens initClassLens) {
     // ..., arrayref, index →
     // ..., value
-    frameBuilder.popAndDiscard(factory.objectArrayType, factory.intType);
+    frameBuilder.popAndDiscardInitialized(factory.objectArrayType, factory.intType);
     frameBuilder.push(FrameType.fromMemberType(type, factory));
   }
 }
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 5d64188..cc0cd63 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
@@ -120,6 +120,6 @@
     // ...
     frameBuilder
         .popAndDiscard(FrameType.fromMemberType(type, factory))
-        .popAndDiscard(factory.objectArrayType, factory.intType);
+        .popAndDiscardInitialized(factory.objectArrayType, factory.intType);
   }
 }
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 721f3f7..1e25ca7 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
@@ -26,7 +26,7 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfCheckCast extends CfInstruction {
+public class CfCheckCast extends CfInstruction implements CfTypeInstruction {
 
   private final DexType type;
 
@@ -34,11 +34,27 @@
     this.type = type;
   }
 
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
   public DexType getType() {
     return type;
   }
 
   @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfCheckCast(newType);
+  }
+
+  @Override
   public int getCompareToId() {
     return Opcodes.CHECKCAST;
   }
@@ -102,6 +118,6 @@
       InitClassLens initClassLens) {
     // ..., objectref →
     // ..., objectref
-    frameBuilder.popAndDiscard(factory.objectType).push(type);
+    frameBuilder.popAndDiscardInitialized(factory.objectType).push(type);
   }
 }
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 863dce0..a6a0933 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
@@ -26,7 +26,7 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
 
-public class CfConstClass extends CfInstruction {
+public class CfConstClass extends CfInstruction implements CfTypeInstruction {
 
   private final DexType type;
 
@@ -45,11 +45,27 @@
     return type.acceptCompareTo(((CfConstClass) other).type, visitor);
   }
 
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
   public DexType getType() {
     return type;
   }
 
   @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfConstClass(newType);
+  }
+
+  @Override
   public void write(
       AppView<?> appView,
       ProgramMethod context,
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 be6176b..2e8eedf 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
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -189,7 +191,7 @@
       case Opcodes.GETFIELD:
         // ..., objectref →
         // ..., value
-        frameBuilder.popAndDiscard(field.holder).push(field.type);
+        frameBuilder.popAndDiscardInitialized(field.holder).push(field.type);
         return;
       case Opcodes.GETSTATIC:
         // ..., →
@@ -199,12 +201,18 @@
       case Opcodes.PUTFIELD:
         // ..., objectref, value →
         // ...,
-        frameBuilder.popAndDiscard(field.holder, field.type);
+        frameBuilder
+            .popAndDiscardInitialized(field.type)
+            .pop(
+                field.holder,
+                or(
+                    frameBuilder::isUninitializedThisAndTarget,
+                    frameBuilder::isAssignableAndInitialized));
         return;
       case Opcodes.PUTSTATIC:
         // ..., value →
         // ...
-        frameBuilder.pop(field.type);
+        frameBuilder.popAndDiscardInitialized(field.type);
         return;
       default:
         throw new Unreachable("Unexpected opcode " + opcode);
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 713d764..98bc101 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
@@ -371,6 +371,14 @@
     return stack.size();
   }
 
+  public int computeStackSize() {
+    int size = 0;
+    for (FrameType frameType : stack) {
+      size += frameType.isWide() ? 2 : 1;
+    }
+    return size;
+  }
+
   private Object[] computeStackTypes(int stackCount, GraphLens graphLens, NamingLens namingLens) {
     assert stackCount == stack.size();
     if (stackCount == 0) {
@@ -452,7 +460,7 @@
       DexType returnType,
       DexItemFactory factory,
       InitClassLens initClassLens) {
-    frameBuilder.verifyFrameAndSet(this);
+    frameBuilder.checkFrameAndSet(this);
   }
 
   public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
index 7ce2440..9272226 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.cf.code;
 
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -39,6 +41,7 @@
   private final DexItemFactory factory;
   private final List<CfTryCatch> tryCatchRanges;
   private final GraphLens graphLens;
+  private final int maxStackHeight;
 
   private final Deque<CfTryCatch> currentCatchRanges = new ArrayDeque<>();
   private final Set<CfLabel> tryCatchRangeLabels;
@@ -49,13 +52,15 @@
       List<CfTryCatch> tryCatchRanges,
       BiPredicate<DexType, DexType> isJavaAssignable,
       DexItemFactory factory,
-      GraphLens graphLens) {
+      GraphLens graphLens,
+      int maxStackHeight) {
     this.context = context;
     this.stateMap = stateMap;
     this.tryCatchRanges = tryCatchRanges;
     this.isJavaAssignable = isJavaAssignable;
     this.factory = factory;
     this.graphLens = graphLens;
+    this.maxStackHeight = maxStackHeight;
     throwStack = ImmutableDeque.of(FrameType.initialized(factory.throwableType));
     // Compute all labels that marks a start or end to catch ranges.
     tryCatchRangeLabels = Sets.newIdentityHashSet();
@@ -70,50 +75,60 @@
   }
 
   public FrameType readLocal(int index, DexType expectedType) {
-    verifyFrameIsSet();
+    checkFrameIsSet();
     FrameType frameType = currentFrame.getLocals().get(index);
     if (frameType == null) {
       throw CfCodeStackMapValidatingException.error("No local at index " + index);
     }
-    verifyIsAssignable(frameType, expectedType);
+    checkIsAssignable(
+        frameType,
+        expectedType,
+        or(
+            this::isUninitializedThisAndTarget,
+            this::isUninitializedNewAndTarget,
+            this::isAssignableAndInitialized));
     return frameType;
   }
 
   public void storeLocal(int index, FrameType frameType) {
-    verifyFrameIsSet();
+    checkFrameIsSet();
     currentFrame.getLocals().put(index, frameType);
   }
 
   public FrameType pop() {
-    verifyFrameIsSet();
+    checkFrameIsSet();
     if (currentFrame.getStack().isEmpty()) {
       throw CfCodeStackMapValidatingException.error("Cannot pop() from an empty stack");
     }
     return currentFrame.getStack().removeLast();
   }
 
-  public FrameType pop(DexType expectedType) {
+  public FrameType popInitialized(DexType expectedType) {
+    return pop(expectedType, this::isAssignableAndInitialized);
+  }
+
+  public FrameType pop(DexType expectedType, BiPredicate<FrameType, DexType> isAssignable) {
     FrameType frameType = pop();
-    verifyIsAssignable(frameType, expectedType);
+    checkIsAssignable(frameType, expectedType, isAssignable);
     return frameType;
   }
 
-  public CfFrameVerificationHelper popAndDiscard(DexType... expectedTypes) {
-    verifyFrameIsSet();
+  public CfFrameVerificationHelper popAndDiscardInitialized(DexType... expectedTypes) {
+    checkFrameIsSet();
     for (int i = expectedTypes.length - 1; i >= 0; i--) {
-      pop(expectedTypes[i]);
+      popInitialized(expectedTypes[i]);
     }
     return this;
   }
 
   public FrameType pop(FrameType expectedType) {
     FrameType frameType = pop();
-    verifyIsAssignable(frameType, expectedType);
+    checkIsAssignable(frameType, expectedType);
     return frameType;
   }
 
   public CfFrameVerificationHelper popAndDiscard(FrameType... expectedTypes) {
-    verifyFrameIsSet();
+    checkFrameIsSet();
     for (int i = expectedTypes.length - 1; i >= 0; i--) {
       pop(expectedTypes[i]);
     }
@@ -121,18 +136,30 @@
   }
 
   public void popAndInitialize(DexType context, DexType methodHolder) {
-    verifyFrameIsSet();
-    FrameType objectRef = pop(factory.objectType);
+    checkFrameIsSet();
+    FrameType objectRef =
+        pop(
+            factory.objectType,
+            or(this::isUninitializedThisAndTarget, this::isUninitializedNewAndTarget));
     CfFrame newFrame =
         currentFrame.markInstantiated(
             objectRef, objectRef.isUninitializedNew() ? methodHolder : context);
     setNoFrame();
-    verifyFrameAndSet(newFrame);
+    checkFrameAndSet(newFrame);
   }
 
   public CfFrameVerificationHelper push(FrameType type) {
-    verifyFrameIsSet();
+    checkFrameIsSet();
     currentFrame.getStack().addLast(type);
+    if (currentFrame.computeStackSize() > maxStackHeight) {
+      throw CfCodeStackMapValidatingException.error(
+          "The max stack height of "
+              + maxStackHeight
+              + " is violated when pushing type "
+              + type
+              + " to existing stack of size "
+              + currentFrame.getStack().size());
+    }
     return this;
   }
 
@@ -153,7 +180,7 @@
           if (destinationFrame == null) {
             throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
           }
-          verifyStackIsAssignable(
+          checkStackIsAssignable(
               destinationFrame.getStack(), throwStack, factory, isJavaAssignable);
         }
       }
@@ -162,15 +189,15 @@
     return this;
   }
 
-  private void verifyFrameIsSet() {
+  private void checkFrameIsSet() {
     if (currentFrame == NO_FRAME) {
       throw CfCodeStackMapValidatingException.error("Unexpected state change");
     }
   }
 
-  public void verifyFrameAndSet(CfFrame newFrame) {
+  public void checkFrameAndSet(CfFrame newFrame) {
     if (currentFrame != NO_FRAME) {
-      verifyFrame(newFrame);
+      checkFrame(newFrame);
     }
     setFrame(newFrame);
   }
@@ -182,7 +209,7 @@
             new Int2ReferenceAVLTreeMap<>(frame.getLocals()), new ArrayDeque<>(frame.getStack()));
   }
 
-  public void verifyExceptionEdges() {
+  public void checkExceptionEdges() {
     for (CfTryCatch currentCatchRange : currentCatchRanges) {
       for (CfLabel target : currentCatchRange.targets) {
         CfFrame destinationFrame = stateMap.get(target);
@@ -192,7 +219,7 @@
         // We have to check all current handler targets have assignable locals and a 1-element
         // stack assignable to throwable. It is not required that the the thrown error is
         // handled.
-        verifyLocalsIsAssignable(
+        checkLocalsIsAssignable(
             currentFrame.getLocals(), destinationFrame.getLocals(), factory, isJavaAssignable);
       }
     }
@@ -202,19 +229,19 @@
     return currentFrame;
   }
 
-  public void verifyTarget(CfLabel label) {
-    verifyFrame(stateMap.get(label));
+  public void checkTarget(CfLabel label) {
+    checkFrame(stateMap.get(label));
   }
 
-  public void verifyFrame(CfFrame destinationFrame) {
+  public void checkFrame(CfFrame destinationFrame) {
     if (destinationFrame == null) {
       throw CfCodeStackMapValidatingException.error("No destination frame");
     }
-    verifyFrame(destinationFrame.getLocals(), destinationFrame.getStack());
+    checkFrame(destinationFrame.getLocals(), destinationFrame.getStack());
   }
 
-  public void verifyFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
-    verifyIsAssignable(
+  public void checkFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
+    checkIsAssignable(
         currentFrame.getLocals(),
         currentFrame.getStack(),
         locals,
@@ -227,30 +254,37 @@
     currentFrame = NO_FRAME;
   }
 
-  public void clearStack() {
-    verifyFrameIsSet();
-    currentFrame.getStack().clear();
+  public boolean isUninitializedThisAndTarget(FrameType source, DexType target) {
+    if (!source.isUninitializedThis()) {
+      return false;
+    }
+    return target == factory.objectType || graphLens.lookupClassType(target) == context;
   }
 
-  public void verifyIsAssignable(FrameType source, DexType target) {
+  public boolean isUninitializedNewAndTarget(FrameType source, DexType target) {
+    if (!source.isUninitializedNew()) {
+      return false;
+    }
+    return target == factory.objectType || graphLens.lookupClassType(target) == context;
+  }
+
+  public boolean isAssignableAndInitialized(FrameType source, DexType target) {
     if (!source.isInitialized()) {
-      DexType rewrittenTarget = graphLens.lookupClassType(target);
-      if (source.isUninitializedThis() && rewrittenTarget == context) {
-        return;
-      }
-      if (rewrittenTarget == factory.objectType) {
-        return;
-      }
-      throw CfCodeStackMapValidatingException.error(
-          "The expected type " + source + " is not assignable to " + target.toSourceString());
+      return false;
     }
-    if (!isJavaAssignable.test(source.getInitializedType(), target)) {
-      throw CfCodeStackMapValidatingException.error(
-          "The expected type " + source + " is not assignable to " + target.toSourceString());
-    }
+    return isJavaAssignable.test(source.getInitializedType(), target);
   }
 
-  public void verifyIsAssignable(FrameType source, FrameType target) {
+  public void checkIsAssignable(
+      FrameType source, DexType target, BiPredicate<FrameType, DexType> predicate) {
+    if (predicate.test(source, target)) {
+      return;
+    }
+    throw CfCodeStackMapValidatingException.error(
+        "The expected type " + source + " is not assignable to " + target.toSourceString());
+  }
+
+  public void checkIsAssignable(FrameType source, FrameType target) {
     if (!canBeAssigned(source, target, factory, isJavaAssignable)) {
       throw CfCodeStackMapValidatingException.error(
           "The expected type " + source + " is not assignable to " + target);
@@ -258,18 +292,18 @@
   }
 
   // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.4.
-  public static void verifyIsAssignable(
+  public static void checkIsAssignable(
       Int2ReferenceSortedMap<FrameType> sourceLocals,
       Deque<FrameType> sourceStack,
       Int2ReferenceSortedMap<FrameType> destLocals,
       Deque<FrameType> destStack,
       DexItemFactory factory,
       BiPredicate<DexType, DexType> isJavaAssignable) {
-    verifyLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
-    verifyStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
+    checkLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
+    checkStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
   }
 
-  private static void verifyLocalsIsAssignable(
+  private static void checkLocalsIsAssignable(
       Int2ReferenceSortedMap<FrameType> sourceLocals,
       Int2ReferenceSortedMap<FrameType> destLocals,
       DexItemFactory factory,
@@ -305,7 +339,7 @@
     }
   }
 
-  private static void verifyStackIsAssignable(
+  private static void checkStackIsAssignable(
       Deque<FrameType> sourceStack,
       Deque<FrameType> destStack,
       DexItemFactory factory,
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 2f4a549..b3043c9 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
@@ -97,7 +97,7 @@
       DexType returnType,
       DexItemFactory factory,
       InitClassLens initClassLens) {
-    frameBuilder.verifyTarget(target);
+    frameBuilder.checkTarget(target);
     frameBuilder.setNoFrame();
   }
 }
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 b331673..7653d51 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
@@ -135,8 +135,8 @@
       InitClassLens initClassLens) {
     // ..., value →
     // ...
-    frameBuilder.pop(
+    frameBuilder.popAndDiscardInitialized(
         type.isObject() ? factory.objectType : type.toPrimitiveType().toDexType(factory));
-    frameBuilder.verifyTarget(target);
+    frameBuilder.checkTarget(target);
   }
 }
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 45b426b..3e5c38a 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
@@ -138,7 +138,7 @@
     // ...
     DexType type =
         this.type.isObject() ? factory.objectType : this.type.toPrimitiveType().toDexType(factory);
-    frameBuilder.popAndDiscard(type, type);
-    frameBuilder.verifyTarget(target);
+    frameBuilder.popAndDiscardInitialized(type, type);
+    frameBuilder.checkTarget(target);
   }
 }
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 815500d..0b8240a 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
@@ -55,6 +55,11 @@
   }
 
   @Override
+  public boolean isInitClass() {
+    return true;
+  }
+
+  @Override
   public void write(
       AppView<?> appView,
       ProgramMethod context,
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 c18eb57..b4d6d43 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
@@ -25,7 +25,7 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfInstanceOf extends CfInstruction {
+public class CfInstanceOf extends CfInstruction implements CfTypeInstruction {
 
   private final DexType type;
 
@@ -33,11 +33,27 @@
     this.type = type;
   }
 
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
   public DexType getType() {
     return type;
   }
 
   @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfInstanceOf(newType);
+  }
+
+  @Override
   public int getCompareToId() {
     return Opcodes.INSTANCEOF;
   }
@@ -110,6 +126,6 @@
       InitClassLens initClassLens) {
     // ..., objectref →
     // ..., result
-    frameBuilder.popAndDiscard(factory.objectType).push(factory.intType);
+    frameBuilder.popAndDiscardInitialized(factory.objectType).push(factory.intType);
   }
 }
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 c69ba09..1009080 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
@@ -221,6 +221,18 @@
     return false;
   }
 
+  public CfTypeInstruction asTypeInstruction() {
+    return null;
+  }
+
+  public boolean isTypeInstruction() {
+    return false;
+  }
+
+  public boolean isInitClass() {
+    return false;
+  }
+
   public CfDexItemBasedConstString asDexItemBasedConstString() {
     return null;
   }
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 db81a5f..87cae25 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
@@ -354,11 +354,11 @@
     // OR, for static method calls:
     // ..., [arg1, [arg2 ...]] →
     // ...
-    frameBuilder.popAndDiscard(this.method.proto.parameters.values);
+    frameBuilder.popAndDiscardInitialized(this.method.proto.parameters.values);
     if (opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(factory)) {
       frameBuilder.popAndInitialize(context, method.holder);
     } else if (opcode != Opcodes.INVOKESTATIC) {
-      frameBuilder.pop(method.holder);
+      frameBuilder.popInitialized(method.holder);
     }
     if (this.method.proto.returnType != factory.voidType) {
       frameBuilder.push(this.method.proto.returnType);
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 88ffb82..3a98a8e 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
@@ -167,7 +167,7 @@
       InitClassLens initClassLens) {
     // ..., [arg1, [arg2 ...]] →
     // ...
-    frameBuilder.popAndDiscard(callSite.methodProto.parameters.values);
+    frameBuilder.popAndDiscardInitialized(callSite.methodProto.parameters.values);
     if (callSite.methodProto.returnType != factory.voidType) {
       frameBuilder.push(callSite.methodProto.returnType);
     }
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 9180511..682c857 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
@@ -27,7 +27,7 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfMultiANewArray extends CfInstruction {
+public class CfMultiANewArray extends CfInstruction implements CfTypeInstruction {
 
   private final DexType type;
   private final int dimensions;
@@ -41,10 +41,26 @@
     this.dimensions = dimensions;
   }
 
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
   public DexType getType() {
     return type;
   }
 
+  @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfMultiANewArray(newType, dimensions);
+  }
+
   public int getDimensions() {
     return dimensions;
   }
@@ -114,7 +130,7 @@
     // ..., count1, [count2, ...] →
     // ..., arrayref
     for (int i = 0; i < dimensions; i++) {
-      frameBuilder.pop(factory.intType);
+      frameBuilder.popInitialized(factory.intType);
     }
     frameBuilder.push(type);
   }
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 7140404..53fc40c 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
@@ -26,7 +26,7 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfNew extends CfInstruction {
+public class CfNew extends CfInstruction implements CfTypeInstruction {
 
   private final DexType type;
 
@@ -34,11 +34,27 @@
     this.type = type;
   }
 
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
   public DexType getType() {
     return type;
   }
 
   @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfNew(newType);
+  }
+
+  @Override
   public int getCompareToId() {
     return Opcodes.NEW;
   }
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 bd36756..ad50c17 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
@@ -28,7 +28,7 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfNewArray extends CfInstruction {
+public class CfNewArray extends CfInstruction implements CfTypeInstruction {
 
   private final DexType type;
 
@@ -37,11 +37,27 @@
     this.type = type;
   }
 
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
   public DexType getType() {
     return type;
   }
 
   @Override
+  public CfInstruction withType(DexType newType) {
+    return new CfNewArray(newType);
+  }
+
+  @Override
   public int getCompareToId() {
     return type.isPrimitiveArrayType() ? Opcodes.NEWARRAY : Opcodes.ANEWARRAY;
   }
@@ -150,6 +166,6 @@
     // ..., count →
     // ..., arrayref
     assert type.isArrayType();
-    frameBuilder.popAndDiscard(factory.intType).push(type);
+    frameBuilder.popAndDiscardInitialized(factory.intType).push(type);
   }
 }
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 51d1283..2b4eed5 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
@@ -113,7 +113,7 @@
       DexItemFactory factory,
       InitClassLens initClassLens) {
     assert returnType != null;
-    frameBuilder.popAndDiscard(returnType);
+    frameBuilder.popAndDiscardInitialized(returnType);
     frameBuilder.setNoFrame();
   }
 }
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 bfdbe0a..4fd9829 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
@@ -368,7 +368,7 @@
         // ...
         final FrameType pop = frameBuilder.pop();
         if (!pop.isWide()) {
-          frameBuilder.verifyIsAssignable(pop, FrameType.oneWord());
+          frameBuilder.checkIsAssignable(pop, FrameType.oneWord());
           frameBuilder.pop(FrameType.oneWord());
         }
         return;
@@ -399,7 +399,7 @@
           FrameType value1 = frameBuilder.pop(FrameType.oneWord());
           FrameType value2 = frameBuilder.pop();
           if (!value2.isWide()) {
-            frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+            frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
             FrameType value3 = frameBuilder.pop(FrameType.oneWord());
             frameBuilder.push(value1).push(value3);
           } else {
@@ -417,7 +417,7 @@
           // ..., value, value
           FrameType value1 = frameBuilder.pop();
           if (!value1.isWide()) {
-            frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+            frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
             FrameType value2 = frameBuilder.pop(FrameType.oneWord());
             frameBuilder.push(value2).push(value1).push(value2);
           } else {
@@ -436,7 +436,7 @@
           FrameType value1 = frameBuilder.pop();
           FrameType value2 = frameBuilder.pop(FrameType.oneWord());
           if (!value1.isWide()) {
-            frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+            frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
             FrameType value3 = frameBuilder.pop(FrameType.oneWord());
             frameBuilder.push(value2).push(value1).push(value3);
           } else {
@@ -466,9 +466,9 @@
             FrameType value3 = frameBuilder.pop();
             if (!value3.isWide()) {
               // (1)
-              frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
-              frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
-              frameBuilder.verifyIsAssignable(value3, FrameType.oneWord());
+              frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
+              frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
+              frameBuilder.checkIsAssignable(value3, FrameType.oneWord());
               FrameType value4 = frameBuilder.pop(FrameType.oneWord());
               frameBuilder
                   .push(value2)
@@ -479,13 +479,13 @@
                   .push(value1);
             } else {
               // (3)
-              frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
-              frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+              frameBuilder.checkIsAssignable(value1, FrameType.oneWord());
+              frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
               frameBuilder.push(value2).push(value1).push(value3).push(value2).push(value1);
             }
           } else if (!value2.isWide()) {
             // (2)
-            frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+            frameBuilder.checkIsAssignable(value2, FrameType.oneWord());
             FrameType value3 = frameBuilder.pop(FrameType.oneWord());
             frameBuilder.push(value1).push(value3).push(value2).push(value1);
           } else {
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 d74eb39..b87c87c 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
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import static com.android.tools.r8.utils.BiPredicateUtils.or;
+
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
@@ -129,24 +131,34 @@
     FrameType pop = frameBuilder.pop();
     switch (type) {
       case OBJECT:
-        frameBuilder.verifyIsAssignable(pop, factory.objectType);
+        frameBuilder.checkIsAssignable(
+            pop,
+            factory.objectType,
+            or(
+                frameBuilder::isUninitializedThisAndTarget,
+                frameBuilder::isUninitializedNewAndTarget,
+                frameBuilder::isAssignableAndInitialized));
         frameBuilder.storeLocal(var, pop);
         return;
       case INT:
-        frameBuilder.verifyIsAssignable(pop, factory.intType);
+        frameBuilder.checkIsAssignable(
+            pop, factory.intType, frameBuilder::isAssignableAndInitialized);
         frameBuilder.storeLocal(var, FrameType.initialized(factory.intType));
         return;
       case FLOAT:
-        frameBuilder.verifyIsAssignable(pop, factory.floatType);
+        frameBuilder.checkIsAssignable(
+            pop, factory.floatType, frameBuilder::isAssignableAndInitialized);
         frameBuilder.storeLocal(var, FrameType.initialized(factory.floatType));
         return;
       case LONG:
-        frameBuilder.verifyIsAssignable(pop, factory.longType);
+        frameBuilder.checkIsAssignable(
+            pop, factory.longType, frameBuilder::isAssignableAndInitialized);
         frameBuilder.storeLocal(var, FrameType.initialized(factory.longType));
         frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.longType));
         return;
       case DOUBLE:
-        frameBuilder.verifyIsAssignable(pop, factory.doubleType);
+        frameBuilder.checkIsAssignable(
+            pop, factory.doubleType, frameBuilder::isAssignableAndInitialized);
         frameBuilder.storeLocal(var, FrameType.initialized(factory.doubleType));
         frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.doubleType));
         return;
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 f07011a..b84fc14 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
@@ -149,10 +149,10 @@
       InitClassLens initClassLens) {
     // ..., index/key →
     // ...
-    frameBuilder.pop(factory.intType);
-    frameBuilder.verifyTarget(defaultTarget);
+    frameBuilder.popInitialized(factory.intType);
+    frameBuilder.checkTarget(defaultTarget);
     for (CfLabel target : targets) {
-      frameBuilder.verifyTarget(target);
+      frameBuilder.checkTarget(target);
     }
     frameBuilder.setNoFrame();
   }
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 aad20a8..90d25c5 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
@@ -95,7 +95,7 @@
       InitClassLens initClassLens) {
     // ..., objectref →
     // objectref
-    frameBuilder.pop(factory.throwableType);
+    frameBuilder.popInitialized(factory.throwableType);
     // The exception edges are verified in CfCode since this is a throwing instruction.
     frameBuilder.setNoFrame();
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java
new file mode 100644
index 0000000..1762c7a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTypeInstruction.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.graph.DexType;
+
+public interface CfTypeInstruction {
+
+  DexType getType();
+
+  CfInstruction withType(DexType newType);
+}
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 da3b04c..fa7e21d 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -643,7 +643,10 @@
     // for all code objects and write the processed results into that map.
     Map<DexEncodedMethod, DexCode> codeMapping = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1;
+      // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never
+      //  duplicated in outputs.
+      boolean isSharedSynthetic =
+          appView.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1;
       clazz.forEachMethod(
           method -> {
             DexCode code =
@@ -672,7 +675,7 @@
             provider,
             objectMapping,
             codeMapping,
-            appView.appInfo().app(),
+            appView.appInfo(),
             options,
             namingLens,
             desugaredLibraryCodeToKeep);
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 bbe4a90..adfab44 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.errors.InvokeCustomDiagnostic;
 import com.android.tools.r8.errors.PrivateInterfaceMethodDiagnostic;
 import com.android.tools.r8.errors.StaticInterfaceMethodDiagnostic;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -99,6 +100,7 @@
 
   private final ObjectToOffsetMapping mapping;
   private final MethodToCodeObjectMapping codeMapping;
+  private final AppInfo appInfo;
   private final DexApplication application;
   private final InternalOptions options;
   private final GraphLens graphLens;
@@ -112,13 +114,14 @@
       ByteBufferProvider provider,
       ObjectToOffsetMapping mapping,
       MethodToCodeObjectMapping codeMapping,
-      DexApplication application,
+      AppInfo appInfo,
       InternalOptions options,
       NamingLens namingLens,
       CodeToKeep desugaredLibraryCodeToKeep) {
     this.mapping = mapping;
     this.codeMapping = codeMapping;
-    this.application = application;
+    this.appInfo = appInfo;
+    this.application = appInfo.app();
     this.options = options;
     this.graphLens = mapping.getGraphLens();
     this.namingLens = namingLens;
@@ -680,7 +683,8 @@
     dest.putUleb128(clazz.getMethodCollection().numberOfVirtualMethods());
     writeEncodedFields(clazz.staticFields());
     writeEncodedFields(clazz.instanceFields());
-    boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1;
+    boolean isSharedSynthetic =
+        appInfo.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1;
     writeEncodedMethods(clazz.directMethods(), isSharedSynthetic);
     writeEncodedMethods(clazz.virtualMethods(), isSharedSynthetic);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index 84459b4..29987e7 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -304,7 +304,8 @@
       if (getClassNameSeparator() != '/') {
         javaPackage = javaPackage.replace(getClassNameSeparator(), '/');
       }
-      String minifiedJavaPackage = namingLens.lookupPackageName(javaPackage);
+      String packageName = appView.graphLens().lookupPackageName(javaPackage);
+      String minifiedJavaPackage = namingLens.lookupPackageName(packageName);
       if (!javaPackage.equals(minifiedJavaPackage)) {
         outputRangeFromInput(outputFrom, from);
         outputJavaType(
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 135fcb4..e44973a 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -36,7 +36,6 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -330,7 +329,11 @@
       Collection<DexProgramClass> synthetics = new ArrayList<>();
       // Assign dedicated virtual files for all program classes.
       for (DexProgramClass clazz : appView.appInfo().classes()) {
-        if (!combineSyntheticClassesWithPrimaryClass || clazz.getSynthesizedFrom().isEmpty()) {
+        Collection<DexType> contexts =
+            appView.getSyntheticItems().getSynthesizingContexts(clazz.getType());
+        // TODO(b/181636450): Simplify this making use of the assumption that synthetics are never
+        //  duplicated.
+        if (!combineSyntheticClassesWithPrimaryClass || contexts.isEmpty()) {
           VirtualFile file =
               new VirtualFile(
                   virtualFiles.size(),
@@ -350,7 +353,9 @@
         }
       }
       for (DexProgramClass synthetic : synthetics) {
-        for (DexProgramClass inputType : synthetic.getSynthesizedFrom()) {
+        for (DexType context :
+            appView.getSyntheticItems().getSynthesizingContexts(synthetic.getType())) {
+          DexProgramClass inputType = appView.definitionForProgramType(context);
           VirtualFile file = files.get(inputType);
           file.addClass(synthetic);
           file.commitTransaction();
@@ -471,23 +476,6 @@
           classes.removeAll(featureClasses);
         }
       }
-      List<DexProgramClass> toRemove = new ArrayList<>();
-      for (DexProgramClass dexProgramClass : classes) {
-        if (appView.getSyntheticItems().isLegacySyntheticClass(dexProgramClass)) {
-          Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
-          if (!synthesizedFrom.isEmpty()) {
-            DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
-            FeatureSplit featureSplit =
-                classToFeatureSplitMap.getFeatureSplit(from, appView.getSyntheticItems());
-            if (!featureSplit.isBase()) {
-              Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
-              dexProgramClasses.add(dexProgramClass);
-              toRemove.add(dexProgramClass);
-            }
-          }
-        }
-      }
-      classes.removeAll(toRemove);
       return featureSplitClasses;
     }
 
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 f2064b3..5d62aae 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IterableUtils;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -120,20 +121,22 @@
     return syntheticItems;
   }
 
-  public void addSynthesizedClass(DexProgramClass clazz, boolean addToMainDex) {
+  public void addSynthesizedClassForLibraryDesugaring(DexProgramClass clazz) {
     assert checkIfObsolete();
-    syntheticItems.addLegacySyntheticClass(clazz);
-    if (addToMainDex) {
-      mainDexInfo.addSyntheticClass(clazz);
-    }
+    assert options().desugaredLibraryConfiguration != null;
+    syntheticItems.addLegacySyntheticClassForLibraryDesugaring(clazz);
   }
 
   public void addSynthesizedClass(DexProgramClass clazz, ProgramDefinition context) {
     assert checkIfObsolete();
-    syntheticItems.addLegacySyntheticClass(clazz);
-    if (context != null) {
-      mainDexInfo.addLegacySyntheticClass(clazz, context);
-    }
+    assert context != null;
+    syntheticItems.addLegacySyntheticClass(clazz, context);
+  }
+
+  public void addSynthesizedClass(DexProgramClass clazz, Iterable<DexProgramClass> contexts) {
+    assert checkIfObsolete();
+    assert !IterableUtils.isEmpty(contexts);
+    contexts.forEach(context -> addSynthesizedClass(clazz, context));
   }
 
   public List<DexProgramClass> classes() {
@@ -168,7 +171,7 @@
     }
     DexClass definition = definitionFor(type);
     if (definition != null && !definition.isLibraryClass() && !dependent.isLibraryClass()) {
-      InterfaceMethodRewriter.reportDependencyEdge(dependent, definition, options());
+      InterfaceMethodRewriter.reportDependencyEdge(dependent, definition, this);
     }
     return definition;
   }
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 cd90e16..c8d9834 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -100,14 +100,6 @@
         commit, getClassToFeatureSplitMap(), getMainDexInfo(), getMissingClasses());
   }
 
-  public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(MissingClasses missingClasses) {
-    return new AppInfoWithClassHierarchy(
-        getSyntheticItems().commit(app()),
-        getClassToFeatureSplitMap(),
-        getMainDexInfo(),
-        missingClasses);
-  }
-
   public AppInfoWithClassHierarchy rebuildWithClassHierarchy(
       Function<DexApplication, DexApplication> fn) {
     assert checkIfObsolete();
@@ -123,7 +115,10 @@
     assert getClass() == AppInfoWithClassHierarchy.class;
     assert checkIfObsolete();
     return new AppInfoWithClassHierarchy(
-        getSyntheticItems().commit(app()), classToFeatureSplitMap, mainDexInfo, missingClasses);
+        getSyntheticItems().commit(app()),
+        getClassToFeatureSplitMap(),
+        mainDexInfo,
+        getMissingClasses());
   }
 
   @Override
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 ed43ca9..71b938f 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -67,8 +67,17 @@
 
   public enum StackMapStatus {
     NOT_VERIFIED,
-    INVALID_OR_NOT_PRESENT,
-    VALID
+    NOT_PRESENT,
+    INVALID,
+    VALID;
+
+    public boolean isValid() {
+      return this == VALID || this == NOT_PRESENT;
+    }
+
+    public boolean isInvalidOrNotPresent() {
+      return this == INVALID || this == NOT_PRESENT;
+    }
   }
 
   public static class LocalVariableInfo {
@@ -305,7 +314,8 @@
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     GraphLens graphLens = appView.graphLens();
-    assert verifyFrames(method.getDefinition(), appView, null, false);
+    assert verifyFrames(method.getDefinition(), appView, null, false).isValid()
+        : "Could not validate stack map frames";
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     InitClassLens initClassLens = appView.initClassLens();
     InternalOptions options = appView.options();
@@ -432,7 +442,8 @@
       AppView<?> appView,
       Origin origin,
       boolean shouldApplyCodeRewritings) {
-    if (!verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) {
+    stackMapStatus = verifyFrames(method, appView, origin, shouldApplyCodeRewritings);
+    if (!stackMapStatus.isValid()) {
       ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
       copy.removeIf(CfInstruction::isFrame);
       setInstructions(copy);
@@ -705,16 +716,14 @@
             thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end));
   }
 
-  public boolean verifyFrames(
+  public StackMapStatus verifyFrames(
       DexEncodedMethod method, AppView<?> appView, Origin origin, boolean applyProtoTypeChanges) {
     if (!appView.options().canUseInputStackMaps()
         || appView.options().testing.disableStackMapVerification) {
-      stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
-      return true;
+      return StackMapStatus.NOT_PRESENT;
     }
     if (method.hasClassFileVersion() && method.getClassFileVersion().isLessThan(CfVersion.V1_7)) {
-      stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
-      return true;
+      return StackMapStatus.NOT_PRESENT;
     }
     if (!method.isInstanceInitializer()
         && appView
@@ -722,8 +731,7 @@
             .getOriginalMethodSignature(method.method)
             .isInstanceInitializer(appView.dexItemFactory())) {
       // We cannot verify instance initializers if they are moved.
-      stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
-      return true;
+      return StackMapStatus.NOT_PRESENT;
     }
     // Build a map from labels to frames.
     Map<CfLabel, CfFrame> stateMap = new IdentityHashMap<>();
@@ -790,12 +798,13 @@
             tryCatchRanges,
             isAssignablePredicate(appView),
             appView.dexItemFactory(),
-            appView.graphLens());
+            appView.graphLens(),
+            maxStack);
     if (stateMap.containsKey(null)) {
       assert !shouldComputeInitialFrame();
-      builder.verifyFrameAndSet(stateMap.get(null));
+      builder.checkFrameAndSet(stateMap.get(null));
     } else if (shouldComputeInitialFrame()) {
-      builder.verifyFrameAndSet(
+      builder.checkFrameAndSet(
           new CfFrame(
               computeInitialLocals(context, method, rewrittenDescription), new ArrayDeque<>()));
     }
@@ -807,7 +816,7 @@
         // affect the exceptional transfer (the exception edge is always a singleton stack).
         if (instruction.canThrow()) {
           assert !instruction.isStore();
-          builder.verifyExceptionEdges();
+          builder.checkExceptionEdges();
         }
         instruction.evaluate(
             builder, context, returnType, appView.dexItemFactory(), appView.initClassLens());
@@ -823,18 +832,16 @@
             appView);
       }
     }
-    stackMapStatus = StackMapStatus.VALID;
-    return true;
+    return StackMapStatus.VALID;
   }
 
-  private boolean reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) {
+  private StackMapStatus reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) {
     // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and only
     // started enforcing stack maps from 51 in JVM 8. As a consequence, we have different android
     // libraries that has V1_7 code but has no stack maps. To not fail on compilations we only
     // report a warning.
-    stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
     appView.options().reporter.warning(diagnostics);
-    return false;
+    return StackMapStatus.INVALID;
   }
 
   private boolean finalAndExitInstruction(CfInstruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index abeb404..be553ac6 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -95,7 +95,7 @@
   public int getAsDexAccessFlags() {
     // We unset the super flag here, as it is meaningless in DEX. Furthermore, we add missing
     // abstract to interfaces to work around a javac bug when generating package-info classes.
-    int flags = materialize() & ~Constants.ACC_SUPER;
+    int flags = materialize() & ~Constants.ACC_SUPER & ~Constants.ACC_RECORD;
     if (isInterface()) {
       return flags | Constants.ACC_ABSTRACT;
     }
@@ -186,6 +186,10 @@
     set(Constants.ACC_RECORD);
   }
 
+  public void unsetRecord() {
+    unset(Constants.ACC_RECORD);
+  }
+
   public boolean isSuper() {
     return isSet(Constants.ACC_SUPER);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
index f4f1c31..da0f128 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassDefinition.java
@@ -21,6 +21,8 @@
 
   DexType getType();
 
+  boolean isInterface();
+
   @Override
   default boolean isClass() {
     return true;
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 d5ea16e..d82db4a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -199,6 +199,8 @@
       return self();
     }
 
+    public abstract void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz);
+
     public synchronized T addProgramClasses(Collection<DexProgramClass> classes) {
       programClasses.addAll(classes);
       return self();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index f0745c8..130dbcf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -598,6 +598,7 @@
     return isFinal();
   }
 
+  @Override
   public boolean isInterface() {
     return accessFlags.isInterface();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index c3e6998..8eeda6b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -21,6 +21,10 @@
     this.annotations = annotations;
   }
 
+  public boolean hasAnnotations() {
+    return !annotations().isEmpty();
+  }
+
   public DexAnnotationSet annotations() {
     return annotations;
   }
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 d15bfc7..1a2b411 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -51,23 +51,6 @@
     // TODO(b/171867022): Should the optimization info and member info be part of the definition?
   }
 
-  public DexEncodedField(
-      DexField field,
-      FieldAccessFlags accessFlags,
-      FieldTypeSignature genericSignature,
-      DexAnnotationSet annotations,
-      DexValue staticValue,
-      boolean deprecated) {
-    super(annotations);
-    this.field = field;
-    this.accessFlags = accessFlags;
-    this.staticValue = staticValue;
-    this.deprecated = deprecated;
-    this.genericSignature = genericSignature;
-    assert genericSignature != null;
-    assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
-  }
-
   public DexEncodedField(DexField field, FieldAccessFlags accessFlags) {
     this(field, accessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
   }
@@ -81,6 +64,34 @@
     this(field, accessFlags, genericSignature, annotations, staticValue, false);
   }
 
+  public DexEncodedField(
+      DexField field,
+      FieldAccessFlags accessFlags,
+      FieldTypeSignature genericSignature,
+      DexAnnotationSet annotations,
+      DexValue staticValue,
+      boolean deprecated) {
+    this(field, accessFlags, genericSignature, annotations, staticValue, deprecated, false);
+  }
+
+  public DexEncodedField(
+      DexField field,
+      FieldAccessFlags accessFlags,
+      FieldTypeSignature genericSignature,
+      DexAnnotationSet annotations,
+      DexValue staticValue,
+      boolean deprecated,
+      boolean d8R8Synthesized) {
+    super(annotations, d8R8Synthesized);
+    this.field = field;
+    this.accessFlags = accessFlags;
+    this.staticValue = staticValue;
+    this.deprecated = deprecated;
+    this.genericSignature = genericSignature;
+    assert genericSignature != null;
+    assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
+  }
+
   @Override
   public StructuralMapping<DexEncodedField> getStructuralMapping() {
     return DexEncodedField::specify;
@@ -235,10 +246,6 @@
     return accessFlags.isVolatile();
   }
 
-  public boolean hasAnnotation() {
-    return !annotations().isEmpty();
-  }
-
   public boolean hasExplicitStaticValue() {
     assert accessFlags.isStatic();
     return staticValue != null;
@@ -356,6 +363,8 @@
     private FieldTypeSignature genericSignature;
     private DexValue staticValue;
     private FieldOptimizationInfo optimizationInfo;
+    private boolean deprecated;
+    private boolean d8R8Synthesized;
     private Consumer<DexEncodedField> buildConsumer = ConsumerUtils.emptyConsumer();
 
     Builder(DexEncodedField from) {
@@ -370,6 +379,8 @@
           from.optimizationInfo.isDefaultFieldOptimizationInfo()
               ? DefaultFieldOptimizationInfo.getInstance()
               : from.optimizationInfo.mutableCopy();
+      deprecated = from.isDeprecated();
+      d8R8Synthesized = from.isD8R8Synthesized();
     }
 
     public Builder apply(Consumer<Builder> consumer) {
@@ -397,7 +408,14 @@
 
     DexEncodedField build() {
       DexEncodedField dexEncodedField =
-          new DexEncodedField(field, accessFlags, genericSignature, annotations, staticValue);
+          new DexEncodedField(
+              field,
+              accessFlags,
+              genericSignature,
+              annotations,
+              staticValue,
+              deprecated,
+              d8R8Synthesized);
       if (optimizationInfo.isMutableFieldOptimizationInfo()) {
         dexEncodedField.setOptimizationInfo(optimizationInfo.asMutableFieldOptimizationInfo());
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index d5c5135..7d66bb6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -8,8 +8,17 @@
 public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
     extends DexDefinition {
 
-  public DexEncodedMember(DexAnnotationSet annotations) {
+  // This flag indicates if this member has been synthesized by D8/R8. Such members do not require
+  // a proguard mapping file entry. This flag is different from the synthesized access flag. When a
+  // non synthesized member is inlined into a synthesized member, the member no longer has the
+  // synthesized access flag, but the d8R8Synthesized flag is still there. Members can also have
+  // the synthesized access flag prior to D8/R8 compilation, in which case d8R8Synthesized is not
+  // set.
+  private final boolean d8R8Synthesized;
+
+  public DexEncodedMember(DexAnnotationSet annotations, boolean d8R8Synthesized) {
     super(annotations);
+    this.d8R8Synthesized = d8R8Synthesized;
   }
 
   public abstract KotlinMemberLevelInfo getKotlinMemberInfo();
@@ -25,6 +34,10 @@
   @Override
   public abstract R getReference();
 
+  public boolean isD8R8Synthesized() {
+    return d8R8Synthesized;
+  }
+
   @Override
   public boolean isDexEncodedMember() {
     return true;
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 5c9fc0f..470bee9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -160,7 +160,7 @@
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
   private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
-  private CfVersion classFileVersion = null;
+  private CfVersion classFileVersion;
   private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
   /** Generic signature information if the attribute is present in the input */
   private MethodTypeSignature genericSignature;
@@ -177,18 +177,6 @@
   // Any newly added `public` method should check if `this` instance is obsolete.
   private boolean obsolete = false;
 
-  // This flag indicates if the method has been synthesized by D8/R8. Such method do not require
-  // a proguard mapping file entry. This flag is different from the synthesized access flag. When a
-  // non synthesized method is inlined into a synthesized method, the method no longer has the
-  // synthesized access flag, but the d8R8Synthesized flag is still there. Methods can also have
-  // the synthesized access flag prior to D8/R8 compilation, in which case d8R8Synthesized is not
-  // set.
-  private final boolean d8R8Synthesized;
-
-  public boolean isD8R8Synthesized() {
-    return d8R8Synthesized;
-  }
-
   private void checkIfObsolete() {
     assert !obsolete;
   }
@@ -220,6 +208,10 @@
     return getReference().getParameter(argumentIndex - 1);
   }
 
+  public int getNumberOfArguments() {
+    return getReference().getArity() + BooleanUtils.intValue(isInstance());
+  }
+
   public CompilationState getCompilationState() {
     return compilationState;
   }
@@ -319,7 +311,7 @@
       boolean d8R8Synthesized,
       CfVersion classFileVersion,
       boolean deprecated) {
-    super(annotations);
+    super(annotations, d8R8Synthesized);
     this.method = method;
     this.accessFlags = accessFlags;
     this.deprecated = deprecated;
@@ -327,7 +319,6 @@
     this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     this.classFileVersion = classFileVersion;
-    this.d8R8Synthesized = d8R8Synthesized;
     assert accessFlags != null;
     assert code == null || !shouldNotHaveCode();
     assert parameterAnnotationsList != null;
@@ -354,15 +345,15 @@
   // Visitor specifying the structure of the method with respect to its "synthetic" content.
   // TODO(b/171867022): Generalize this so that it determines any method in full.
   private static void syntheticSpecify(StructuralSpecification<DexEncodedMethod, ?> spec) {
-    spec.withItem(m -> m.method)
-        .withItem(m -> m.accessFlags)
+    spec.withItem(DexEncodedMethod::getReference)
+        .withItem(DexEncodedMethod::getAccessFlags)
         .withItem(DexDefinition::annotations)
         .withItem(m -> m.parameterAnnotationsList)
         .withNullableItem(m -> m.classFileVersion)
-        .withBool(m -> m.d8R8Synthesized)
+        .withBool(DexEncodedMember::isD8R8Synthesized)
         // TODO(b/171867022): Make signatures structural and include it in the definition.
         .withAssert(m -> m.genericSignature.hasNoSignature())
-        .withAssert(m1 -> m1.code != null)
+        .withAssert(DexEncodedMethod::hasCode)
         .withCustomItem(
             DexEncodedMethod::getCode,
             DexEncodedMethod::compareCodeObject,
@@ -1547,7 +1538,7 @@
     private Consumer<DexEncodedMethod> buildConsumer = ConsumerUtils.emptyConsumer();
 
     private Builder(DexEncodedMethod from) {
-      this(from, from.d8R8Synthesized);
+      this(from, from.isD8R8Synthesized());
     }
 
     private Builder(DexEncodedMethod from, boolean d8R8Synthesized) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e76409d..2ce742a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME;
 import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
 import static com.android.tools.r8.ir.desugar.LambdaClass.LAMBDA_INSTANCE_FIELD_NAME;
 
@@ -215,6 +216,7 @@
   public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
   public final DexString recordDescriptor = createString("Ljava/lang/Record;");
+  public final DexString r8RecordDescriptor = createString("Lcom/android/tools/r8/RecordTag;");
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
   public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -346,6 +348,7 @@
   public final DexType stringArrayType = createStaticallyKnownType(stringArrayDescriptor);
   public final DexType objectType = createStaticallyKnownType(objectDescriptor);
   public final DexType recordType = createStaticallyKnownType(recordDescriptor);
+  public final DexType r8RecordType = createStaticallyKnownType(r8RecordDescriptor);
   public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
   public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
   public final DexType enumType = createStaticallyKnownType(enumDescriptor);
@@ -509,6 +512,7 @@
   public final LongMembers longMembers = new LongMembers();
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
   public final ObjectMembers objectMembers = new ObjectMembers();
+  public final RecordMembers recordMembers = new RecordMembers();
   public final ShortMembers shortMembers = new ShortMembers();
   public final StringMembers stringMembers = new StringMembers();
   public final DoubleMembers doubleMembers = new DoubleMembers();
@@ -1203,9 +1207,23 @@
     }
   }
 
+  public class RecordMembers {
+    public final DexMethod init = createMethod(recordType, createProto(voidType), "<init>");
+    public final DexMethod equals =
+        createMethod(recordType, createProto(booleanType, objectType), "equals");
+    public final DexMethod hashCode = createMethod(recordType, createProto(intType), "hashCode");
+    public final DexMethod toString = createMethod(recordType, createProto(stringType), "toString");
+  }
+
   public class ObjectMembers {
 
     /**
+     * This field is not on {@link Object}, but will be synthesized on horizontally merged classes
+     * as an instance field.
+     */
+    public final DexField classIdField = createField(objectType, intType, CLASS_ID_FIELD_NAME);
+
+    /**
      * This field is not on {@link Object}, but will be synthesized on program classes as a static
      * field, for the compiler to have a principled way to trigger the initialization of a given
      * class.
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 11e5957..a4ccb8e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -28,9 +28,6 @@
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -50,7 +47,6 @@
   public static final DexProgramClass[] EMPTY_ARRAY = {};
 
   private final ProgramResource.Kind originKind;
-  private final Collection<DexProgramClass> synthesizedFrom;
   private CfVersion initialClassFileVersion = null;
   private boolean deprecated = false;
   private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO;
@@ -77,50 +73,6 @@
       DexEncodedMethod[] virtualMethods,
       boolean skipNameValidationForTesting,
       ChecksumSupplier checksumSupplier) {
-    this(
-        type,
-        originKind,
-        origin,
-        accessFlags,
-        superType,
-        interfaces,
-        sourceFile,
-        nestHost,
-        nestMembers,
-        enclosingMember,
-        innerClasses,
-        classSignature,
-        classAnnotations,
-        staticFields,
-        instanceFields,
-        directMethods,
-        virtualMethods,
-        skipNameValidationForTesting,
-        checksumSupplier,
-        Collections.emptyList());
-  }
-
-  public DexProgramClass(
-      DexType type,
-      Kind originKind,
-      Origin origin,
-      ClassAccessFlags accessFlags,
-      DexType superType,
-      DexTypeList interfaces,
-      DexString sourceFile,
-      NestHostClassAttribute nestHost,
-      List<NestMemberClassAttribute> nestMembers,
-      EnclosingMethodAttribute enclosingMember,
-      List<InnerClassAttribute> innerClasses,
-      ClassSignature classSignature,
-      DexAnnotationSet classAnnotations,
-      DexEncodedField[] staticFields,
-      DexEncodedField[] instanceFields,
-      DexEncodedMethod[] directMethods,
-      DexEncodedMethod[] virtualMethods,
-      boolean skipNameValidationForTesting,
-      ChecksumSupplier checksumSupplier,
-      Collection<DexProgramClass> synthesizedDirectlyFrom) {
     super(
         sourceFile,
         interfaces,
@@ -143,8 +95,6 @@
     assert classAnnotations != null;
     this.originKind = originKind;
     this.checksumSupplier = checksumSupplier;
-    this.synthesizedFrom = new HashSet<>();
-    synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
   }
 
   @Override
@@ -185,9 +135,7 @@
         .withAssert(c -> c.classSignature == ClassSignature.noSignature())
         .withItemArray(c -> c.staticFields)
         .withItemArray(c -> c.instanceFields)
-        .withItemCollection(DexClass::allMethodsSorted)
-        // TODO(b/168584485): Synthesized-from is being removed (empty for new synthetics).
-        .withAssert(c -> c.synthesizedFrom.isEmpty());
+        .withItemCollection(DexClass::allMethodsSorted);
   }
 
   public void forEachProgramField(Consumer<? super ProgramField> consumer) {
@@ -383,10 +331,6 @@
     }
   }
 
-  public Collection<DexProgramClass> getSynthesizedFrom() {
-    return synthesizedFrom;
-  }
-
   @Override
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     assert getEnclosingMethodAttribute() == null;
@@ -599,7 +543,7 @@
 
   private boolean hasAnnotations(DexEncodedField[] fields) {
     synchronized (fields) {
-      return Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotation);
+      return Arrays.stream(fields).anyMatch(DexEncodedField::hasAnnotations);
     }
   }
 
@@ -609,14 +553,6 @@
     }
   }
 
-  public void addSynthesizedFrom(DexProgramClass clazz) {
-    if (clazz.synthesizedFrom.isEmpty()) {
-      synthesizedFrom.add(clazz);
-    } else {
-      clazz.synthesizedFrom.forEach(this::addSynthesizedFrom);
-    }
-  }
-
   public DexEncodedArray computeStaticValuesArray(NamingLens namingLens) {
     // Fast path to avoid sorting and collection allocation when no non-default values exist.
     if (!hasNonDefaultStaticFieldValues()) {
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 86377b3..0f7b951 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -213,6 +213,37 @@
     }
 
     @Override
+    public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) {
+      addProgramClass(clazz);
+      if (containsType(clazz.type, libraryClasses)) {
+        replaceLibraryClasses(withoutType(clazz.type, libraryClasses));
+        return;
+      }
+      if (containsType(clazz.type, classpathClasses)) {
+        replaceClasspathClasses(withoutType(clazz.type, classpathClasses));
+      }
+    }
+
+    private boolean containsType(DexType type, List<? extends DexClass> classes) {
+      for (DexClass clazz : classes) {
+        if (clazz.type == type) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    private <T extends DexClass> ImmutableList<T> withoutType(DexType type, List<T> classes) {
+      ImmutableList.Builder<T> builder = ImmutableList.builder();
+      for (T clazz : classes) {
+        if (clazz.type != type) {
+          builder.add(clazz);
+        }
+      }
+      return builder.build();
+    }
+
+    @Override
     Builder self() {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 4f07887..1697f43 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -63,6 +63,11 @@
         Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
   }
 
+  public static FieldAccessFlags createPublicFinalSynthetic() {
+    return fromSharedAccessFlags(
+        Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
+  }
+
   public static FieldAccessFlags fromSharedAccessFlags(int access) {
     assert (access & FLAGS) == access;
     return new FieldAccessFlags(access & FLAGS);
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 43e733c..7135cfc 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -364,6 +363,8 @@
     return false;
   }
 
+  public abstract String lookupPackageName(String pkg);
+
   public abstract DexType lookupClassType(DexType type);
 
   public abstract DexType lookupType(DexType type);
@@ -595,7 +596,6 @@
 
   public boolean verifyMappingToOriginalProgram(
       AppView<?> appView, DexApplication originalApplication) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
     Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
     // Collect all original fields and methods for efficient querying.
     Set<DexField> originalFields = Sets.newIdentityHashSet();
@@ -616,11 +616,12 @@
         continue;
       }
       for (DexEncodedField field : clazz.fields()) {
-        // Fields synthesized by R8 are not present in the input, and therefore we do not require
-        // that they can be mapped back to the original program.
+        if (field.isD8R8Synthesized()) {
+          // Fields synthesized by D8/R8 may not be mapped.
+          continue;
+        }
         DexField originalField = getOriginalFieldSignature(field.getReference());
         assert originalFields.contains(originalField)
-                || isD8R8SynthesizedField(field.getReference(), appView)
             : "Unable to map field `"
                 + field.getReference().toSourceString()
                 + "` back to original program";
@@ -638,23 +639,6 @@
     return true;
   }
 
-  private boolean isD8R8SynthesizedField(DexField field, AppView<?> appView) {
-    // TODO(b/167947782): Should be a general check to see if the field is D8/R8 synthesized
-    //  instead of relying on field names.
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    if (field.match(dexItemFactory.objectMembers.clinitField)) {
-      return true;
-    }
-    if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
-      return true;
-    }
-    if (appView.getSyntheticItems().isNonLegacySynthetic(field.getHolderType())
-        && field.getName() == dexItemFactory.lambdaInstanceFieldName) {
-      return true;
-    }
-    return false;
-  }
-
   public abstract static class NonIdentityGraphLens extends GraphLens {
 
     private final DexItemFactory dexItemFactory;
@@ -715,6 +699,11 @@
     }
 
     @Override
+    public String lookupPackageName(String pkg) {
+      return pkg;
+    }
+
+    @Override
     public final DexType lookupType(DexType type) {
       if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) {
         return type;
@@ -831,6 +820,11 @@
     }
 
     @Override
+    public String lookupPackageName(String pkg) {
+      return pkg;
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       return type;
     }
@@ -1051,7 +1045,7 @@
     @Override
     public DexField getRenamedFieldSignature(DexField originalField) {
       DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
-      return fieldMap.getOrDefault(renamedField, renamedField);
+      return internalGetNextFieldSignature(renamedField);
     }
 
     @Override
@@ -1151,6 +1145,10 @@
       return prototypeChanges;
     }
 
+    protected DexField internalGetNextFieldSignature(DexField field) {
+      return fieldMap.getOrDefault(field, field);
+    }
+
     @Override
     protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
       return originalMethodSignatures.getRepresentativeValueOrDefault(method, method);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 35b4574..d8815c7 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -500,7 +500,7 @@
         return;
       }
       // TODO(b/169645628): Support records in all compilation.
-      if (!application.options.canUseRecords()) {
+      if (!application.options.enableExperimentalRecordDesugaring()) {
         throw new CompilationError("Records are not supported", origin);
       }
       // TODO(b/169645628): Change this logic if we start stripping the record components.
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index ba22b16..08bc5f0 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -336,6 +336,7 @@
     private final DebugParsingOptions debugParsingOptions;
     private int maxStack;
     private int maxLocals;
+    private boolean desugaredVisitMultiANewArrayInstruction;
     private List<CfInstruction> instructions;
     private List<CfTryCatch> tryCatchRanges;
     private List<LocalVariableInfo> localVariables;
@@ -386,7 +387,12 @@
       }
       code.setCode(
           new CfCode(
-              method.holder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables));
+              method.holder,
+              desugaredVisitMultiANewArrayInstruction ? Integer.MAX_VALUE : maxStack,
+              maxLocals,
+              instructions,
+              tryCatchRanges,
+              localVariables));
     }
 
     @Override
@@ -948,6 +954,7 @@
       // ..., ref
       visitTypeInsn(Opcodes.CHECKCAST, desc);
       // ..., arrayref(of type : desc)
+      desugaredVisitMultiANewArrayInstruction = true;
     }
 
     @Override
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 7005e06..509a10d 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -226,6 +226,13 @@
     }
 
     @Override
+    public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) {
+      addProgramClass(clazz);
+      classpathClasses.clearType(clazz.type);
+      libraryClasses.clearType(clazz.type);
+    }
+
+    @Override
     public LazyLoadedDexApplication build() {
       return new LazyLoadedDexApplication(
           proguardMap,
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 c0cc708..686d7d5 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -302,8 +302,7 @@
         // We already gave up on tracking the allocation sites for `clazz` previously.
         return false;
       }
-      // We currently only use allocation site information for instance field value propagation.
-      return !clazz.instanceFields().isEmpty();
+      return true;
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 89d0bd7..27f3251 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -113,8 +113,7 @@
             DexEncodedMethod.EMPTY_ARRAY,
             DexEncodedMethod.EMPTY_ARRAY,
             dexItemFactory.getSkipNameValidationForTesting(),
-            clazz.getChecksumSupplier(),
-            fixupSynthesizedFrom(clazz.getSynthesizedFrom()));
+            clazz.getChecksumSupplier());
     newClass.setInstanceFields(fixupFields(clazz.instanceFields()));
     newClass.setStaticFields(fixupFields(clazz.staticFields()));
     newClass.setDirectMethods(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java
deleted file mode 100644
index fa9873a..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/CanOnlyMergeIntoClassPolicy.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import com.android.tools.r8.graph.DexProgramClass;
-
-public abstract class CanOnlyMergeIntoClassPolicy extends SingleClassPolicy {
-  public abstract boolean canOnlyMergeInto(DexProgramClass clazz);
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    // TODO(b/165577835): Allow merging of classes that must be the target of their merge group.
-    return !canOnlyMergeInto(program);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java
index c919e63..d78d74d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInitializerSynthesizedCode.java
@@ -59,11 +59,14 @@
   }
 
   public CfCode synthesizeCode(DexType originalHolder) {
+    // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
+    // the CfCode constructor to ensure that the value passed in is the updated values.
+    List<CfInstruction> instructions = buildInstructions();
     return new CfCode(
         originalHolder,
         maxStack,
         maxLocals,
-        buildInstructions(),
+        instructions,
         Collections.emptyList(),
         Collections.emptyList());
   }
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 11001de..0e7d164 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -26,6 +27,9 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -52,6 +56,8 @@
 
   public static final String CLASS_ID_FIELD_NAME = "$r8$classId";
 
+  private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
+
   private final AppView<AppInfoWithLiveness> appView;
   private final MergeGroup group;
   private final DexItemFactory dexItemFactory;
@@ -131,8 +137,6 @@
       CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion();
       if (cfVersion != null) {
         clinit.upgradeClassFileVersion(cfVersion);
-      } else {
-        assert appView.options().isGeneratingDex();
       }
       classMethodsBuilder.addDirectMethod(clinit);
     }
@@ -194,14 +198,29 @@
   }
 
   void appendClassIdField() {
-    classInstanceFieldsMerger.setClassIdField(
+    boolean deprecated = false;
+    boolean d8R8Synthesized = true;
+    DexEncodedField classIdField =
         new DexEncodedField(
             group.getClassIdField(),
-            FieldAccessFlags.fromSharedAccessFlags(
-                Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC),
+            FieldAccessFlags.createPublicFinalSynthetic(),
             FieldTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
-            null));
+            null,
+            deprecated,
+            d8R8Synthesized);
+
+    // For the $r8$classId synthesized fields, we try to over-approximate the set of values it may
+    // have. For example, for a merge group of size 4, we may compute the set {0, 2, 3}, if the
+    // instances with $r8$classId == 1 ends up dead as a result of optimizations). If no instances
+    // end up being dead, we would compute the set {0, 1, 2, 3}. The latter information does not
+    // provide any value, and therefore we should not save it in the optimization info. In order to
+    // be able to recognize that {0, 1, 2, 3} is useless, we record that the value of the field is
+    // known to be in [0; 3] here.
+    NumberFromIntervalValue abstractValue = new NumberFromIntervalValue(0, group.size() - 1);
+    feedback.recordFieldHasAbstractValue(classIdField, appView, abstractValue);
+
+    classInstanceFieldsMerger.setClassIdField(classIdField);
   }
 
   void mergeStaticFields() {
@@ -219,6 +238,16 @@
     }
   }
 
+  private void mergeAnnotations() {
+    assert group.getClasses().stream().filter(DexDefinition::hasAnnotations).count() <= 1;
+    for (DexProgramClass clazz : group.getSources()) {
+      if (clazz.hasAnnotations()) {
+        group.getTarget().setAnnotations(clazz.annotations());
+        break;
+      }
+    }
+  }
+
   private void mergeInterfaces() {
     DexTypeList previousInterfaces = group.getTarget().getInterfaces();
     Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces);
@@ -241,6 +270,7 @@
     fixAccessFlags();
     appendClassIdField();
 
+    mergeAnnotations();
     mergeInterfaces();
 
     mergeVirtualMethods();
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 d97f12f..05e91a4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,21 +6,20 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.IgnoreSynthetics;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.MinimizeFieldCasts;
-import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
+import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
-import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
 import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
 import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
@@ -30,13 +29,13 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoDifferentMainDexGroups;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDexList;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
 import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
 import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
 import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
 import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
+import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -57,9 +56,7 @@
     assert appView.options().enableInlining;
   }
 
-  // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
   public HorizontalClassMergerResult run(
-      DirectMappedDexApplication.Builder appBuilder,
       RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder());
 
@@ -88,7 +85,7 @@
 
     // Merge the classes.
     SyntheticArgumentClass syntheticArgumentClass =
-        new SyntheticArgumentClass.Builder(appBuilder, appView).build(allMergeClasses);
+        new SyntheticArgumentClass.Builder(appView).build(allMergeClasses);
     applyClassMergers(classMergers, syntheticArgumentClass);
 
     // Generate the graph lens.
@@ -121,37 +118,44 @@
   }
 
   private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
-    return ImmutableList.of(
-        new NotMatchedByNoHorizontalClassMerging(appView),
-        new SameInstanceFields(appView),
-        new NoInterfaces(),
-        new NoAnnotations(),
-        new NoEnums(appView),
-        new CheckAbstractClasses(appView),
-        new IgnoreSynthetics(appView),
-        new NoClassesOrMembersWithAnnotations(appView),
-        new NoInnerClasses(),
-        new NoClassInitializerWithObservableSideEffects(),
-        new NoNativeMethods(),
-        new NoKeepRules(appView),
-        new NoKotlinMetadata(),
-        new NoServiceLoaders(appView),
-        new NotVerticallyMergedIntoSubtype(appView),
-        new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
-        new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
-        new PreventMethodImplementation(appView),
-        new DontInlinePolicy(appView),
-        new PreventMergeIntoMainDexList(appView),
-        new PreventMergeIntoDifferentMainDexGroups(appView),
-        new AllInstantiatedOrUninstantiated(appView),
-        new SameParentClass(),
-        new SameNestHost(),
-        new PreserveMethodCharacteristics(appView),
-        new SameFeatureSplit(appView),
-        new RespectPackageBoundaries(appView),
-        new DontMergeSynchronizedClasses(appView),
-        new MinimizeFieldCasts(),
-        new LimitGroups(appView));
+    List<SingleClassPolicy> singleClassPolicies =
+        ImmutableList.of(
+            new NotMatchedByNoHorizontalClassMerging(appView),
+            new NoAnnotationClasses(),
+            new NoEnums(appView),
+            new NoInnerClasses(),
+            new NoInstanceFieldAnnotations(),
+            new NoInterfaces(),
+            new NoClassInitializerWithObservableSideEffects(),
+            new NoNativeMethods(),
+            new NoKeepRules(appView),
+            new NoKotlinMetadata(),
+            new NoServiceLoaders(appView),
+            new NotVerticallyMergedIntoSubtype(appView),
+            new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
+            new DontInlinePolicy(appView));
+    List<MultiClassPolicy> multiClassPolicies =
+        ImmutableList.of(
+            new SameInstanceFields(appView),
+            new NoClassAnnotationCollisions(),
+            new CheckAbstractClasses(appView),
+            new SyntheticItemsPolicy(appView),
+            new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
+            new PreventMethodImplementation(appView),
+            new PreventMergeIntoDifferentMainDexGroups(appView),
+            new AllInstantiatedOrUninstantiated(appView),
+            new SameParentClass(),
+            new SameNestHost(appView),
+            new PreserveMethodCharacteristics(appView),
+            new SameFeatureSplit(appView),
+            new RespectPackageBoundaries(appView),
+            new DontMergeSynchronizedClasses(appView),
+            new MinimizeFieldCasts(),
+            new LimitGroups(appView));
+    return ImmutableList.<Policy>builder()
+        .addAll(singleClassPolicies)
+        .addAll(multiClassPolicies)
+        .build();
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
new file mode 100644
index 0000000..bd83a89
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerUtils.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+
+public class HorizontalClassMergerUtils {
+
+  public static boolean isClassIdField(AppView<?> appView, DexEncodedField field) {
+    DexField classIdField = appView.dexItemFactory().objectMembers.classIdField;
+    if (field.isD8R8Synthesized() && field.getType().isIntType()) {
+      DexField originalField = appView.graphLens().getOriginalFieldSignature(field.getReference());
+      return originalField.match(classIdField);
+    }
+    assert !appView.graphLens().getOriginalFieldSignature(field.getReference()).match(classIdField);
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index 9e4ebf5..4b9ad6a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -30,6 +30,11 @@
     this.classes = new LinkedList<>();
   }
 
+  public MergeGroup(DexProgramClass clazz) {
+    this();
+    add(clazz);
+  }
+
   public MergeGroup(Collection<DexProgramClass> classes) {
     this();
     addAll(classes);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
index 3cea5125..aa3ca62 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
@@ -15,11 +15,18 @@
   public final Collection<MergeGroup> apply(MergeGroup group) {
     Map<T, MergeGroup> groups = new LinkedHashMap<>();
     for (DexProgramClass clazz : group) {
-      groups.computeIfAbsent(getMergeKey(clazz), ignore -> new MergeGroup()).add(clazz);
+      T mergeKey = getMergeKey(clazz);
+      if (mergeKey != null) {
+        groups.computeIfAbsent(mergeKey, ignore -> new MergeGroup()).add(clazz);
+      }
     }
     removeTrivialGroups(groups.values());
     return groups.values();
   }
 
   public abstract T getMergeKey(DexProgramClass clazz);
+
+  protected final T ineligibleForClassMerging() {
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
index 60c350a..ca0b752 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -4,23 +4,12 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.google.common.collect.Iterables;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -41,7 +30,6 @@
  * <p>This class generates a synthetic class in the package of the first class to be merged.
  */
 public class SyntheticArgumentClass {
-  public static final String SYNTHETIC_CLASS_SUFFIX = "$r8$HorizontalClassMergingArgument";
 
   private final List<DexType> syntheticClassTypes;
 
@@ -55,70 +43,28 @@
 
   public static class Builder {
 
-    private final DirectMappedDexApplication.Builder appBuilder;
     private final AppView<AppInfoWithLiveness> appView;
 
-    Builder(DirectMappedDexApplication.Builder appBuilder, AppView<AppInfoWithLiveness> appView) {
-      this.appBuilder = appBuilder;
+    Builder(AppView<AppInfoWithLiveness> appView) {
       this.appView = appView;
     }
 
-    private DexType synthesizeClass(
-        DexProgramClass context,
-        boolean requiresMainDex,
-        int index) {
-      DexType syntheticClassType =
-          appView
-              .dexItemFactory()
-              .createFreshTypeName(
-                  context.type.addSuffix(SYNTHETIC_CLASS_SUFFIX, appView.dexItemFactory()),
-                  type -> appView.appInfo().definitionForWithoutExistenceAssert(type) == null,
-                  index);
-
-      DexProgramClass clazz =
-          new DexProgramClass(
-              syntheticClassType,
-              null,
-              new SynthesizedOrigin("horizontal class merging", HorizontalClassMerger.class),
-              ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-              appView.dexItemFactory().objectType,
-              DexTypeList.empty(),
-              null,
-              null,
-              Collections.emptyList(),
-              null,
-              Collections.emptyList(),
-              ClassSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              DexEncodedField.EMPTY_ARRAY,
-              DexEncodedField.EMPTY_ARRAY,
-              DexEncodedMethod.EMPTY_ARRAY,
-              DexEncodedMethod.EMPTY_ARRAY,
-              appView.dexItemFactory().getSkipNameValidationForTesting(),
-              DexProgramClass::checksumFromType);
-
-      appBuilder.addSynthesizedClass(clazz);
-      appView.appInfo().addSynthesizedClass(clazz, requiresMainDex);
-      return clazz.type;
+    private DexProgramClass synthesizeClass(DexProgramClass context, SyntheticKind syntheticKind) {
+      return appView
+          .getSyntheticItems()
+          .createFixedClass(syntheticKind, context, appView.dexItemFactory(), builder -> {});
     }
 
     public SyntheticArgumentClass build(Iterable<DexProgramClass> mergeClasses) {
       // Find a fresh name in an existing package.
       DexProgramClass context = mergeClasses.iterator().next();
-
-      // Add as a root to the main dex tracing result if any of the merged classes is a root.
-      // This is needed to satisfy an assertion in the inliner that verifies that we do not inline
-      // methods with references to non-roots into classes that are roots.
-      MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
-      boolean requiresMainDex = Iterables.any(mergeClasses, mainDexInfo::isMainDex);
-
       List<DexType> syntheticArgumentTypes = new ArrayList<>();
-      for (int i = 0;
-          i < appView.options().horizontalClassMergerOptions().getSyntheticArgumentCount();
-          i++) {
-        syntheticArgumentTypes.add(synthesizeClass(context, requiresMainDex, i));
-      }
-
+      syntheticArgumentTypes.add(
+          synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1).getType());
+      syntheticArgumentTypes.add(
+          synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2).getType());
+      syntheticArgumentTypes.add(
+          synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3).getType());
       return new SyntheticArgumentClass(syntheticArgumentTypes);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java
deleted file mode 100644
index e45e2d3..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public class IgnoreSynthetics extends SingleClassPolicy {
-
-  private final AppView<AppInfoWithLiveness> appView;
-
-  public IgnoreSynthetics(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    if (appView.getSyntheticItems().isSyntheticClass(program)) {
-      return appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()
-          && appView.getSyntheticItems().isLegacySyntheticClass(program);
-    }
-    return true;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
index 9190247..1effb8b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
 
-public class NoAnnotations extends SingleClassPolicy {
+public class NoAnnotationClasses extends SingleClassPolicy {
   @Override
   public boolean canMerge(DexProgramClass program) {
     return !program.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
new file mode 100644
index 0000000..9d3d730
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import static com.android.tools.r8.utils.IteratorUtils.createCircularIterator;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class NoClassAnnotationCollisions extends MultiClassPolicy {
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group) {
+    // Create a new merge group for each class that has annotations.
+    List<MergeGroup> newGroups = new LinkedList<>();
+    for (DexProgramClass clazz : group) {
+      if (clazz.hasAnnotations()) {
+        newGroups.add(new MergeGroup(clazz));
+      }
+    }
+
+    // If there were at most one class with annotations, then just return the original merge group.
+    if (newGroups.size() <= 1) {
+      return ImmutableList.of(group);
+    }
+
+    // Otherwise, fill up the new merge groups with the classes that do not have annotations.
+    Iterator<MergeGroup> newGroupsIterator = createCircularIterator(newGroups);
+    for (DexProgramClass clazz : group) {
+      if (!clazz.hasAnnotations()) {
+        newGroupsIterator.next().add(clazz);
+      }
+    }
+    return removeTrivialGroups(newGroups);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
deleted file mode 100644
index f0b2a6e..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesOrMembersWithAnnotations.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
-
-public class NoClassesOrMembersWithAnnotations extends SingleClassPolicy {
-
-  private final HorizontalClassMergerOptions options;
-
-  public NoClassesOrMembersWithAnnotations(AppView<AppInfoWithLiveness> appView) {
-    this.options = appView.options().horizontalClassMergerOptions();
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    return !program.hasClassOrMemberAnnotations();
-  }
-
-  @Override
-  public boolean shouldSkipPolicy() {
-    // TODO(b/179019716): Add support for merging in presence of annotations.
-    return options.skipNoClassesOrMembersWithAnnotationsPolicyForTesting;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
similarity index 62%
copy from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
copy to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
index 9190247..610af7e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotations.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
@@ -4,12 +4,19 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
 
-public class NoAnnotations extends SingleClassPolicy {
+public class NoInstanceFieldAnnotations extends SingleClassPolicy {
+
   @Override
   public boolean canMerge(DexProgramClass program) {
-    return !program.isAnnotation();
+    for (DexEncodedField instanceField : program.instanceFields()) {
+      if (instanceField.hasAnnotations()) {
+        return false;
+      }
+    }
+    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
index 52a1d98..41c240f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
@@ -10,19 +10,23 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.MainDexInfo.MainDexGroup;
+import com.android.tools.r8.synthesis.SyntheticItems;
 
 public class PreventMergeIntoDifferentMainDexGroups
     extends MultiClassSameReferencePolicy<MainDexGroup> {
 
   private final MainDexInfo mainDexInfo;
+  private final SyntheticItems synthetics;
 
   public PreventMergeIntoDifferentMainDexGroups(AppView<AppInfoWithLiveness> appView) {
-    this.mainDexInfo = appView.appInfo().getMainDexInfo();
+    mainDexInfo = appView.appInfo().getMainDexInfo();
+    synthetics = appView.getSyntheticItems();
   }
 
   @Override
   public MainDexGroup getMergeKey(DexProgramClass clazz) {
-    assert !mainDexInfo.isFromList(clazz);
-    return mainDexInfo.getMergeKey(clazz);
+    return mainDexInfo.canMerge(clazz, synthetics)
+        ? mainDexInfo.getMergeKey(clazz, synthetics)
+        : ineligibleForClassMerging();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java
deleted file mode 100644
index 850d02e..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDexList.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexInfo;
-
-public class PreventMergeIntoMainDexList extends SingleClassPolicy {
-
-  private final MainDexInfo mainDexInfo;
-
-  public PreventMergeIntoMainDexList(AppView<AppInfoWithLiveness> appView) {
-    this.mainDexInfo = appView.appInfo().getMainDexInfo();
-  }
-
-  @Override
-  public boolean canMerge(DexProgramClass program) {
-    return mainDexInfo.canMerge(program);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
index 6619878..88d2984 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -4,13 +4,23 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class SameNestHost extends MultiClassSameReferencePolicy<DexType> {
+
+  private final DexItemFactory dexItemFactory;
+
+  public SameNestHost(AppView<AppInfoWithLiveness> appView) {
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
   @Override
   public DexType getMergeKey(DexProgramClass clazz) {
-    return clazz.getNestHost();
+    return clazz.isInANest() ? clazz.getNestHost() : dexItemFactory.objectType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
new file mode 100644
index 0000000..b012897
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy.ClassKind;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+
+public class SyntheticItemsPolicy extends MultiClassSameReferencePolicy<ClassKind> {
+
+  enum ClassKind {
+    SYNTHETIC,
+    NOT_SYNTHETIC
+  }
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public SyntheticItemsPolicy(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public ClassKind getMergeKey(DexProgramClass clazz) {
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+
+    // Allow merging non-synthetics with non-synthetics.
+    if (!syntheticItems.isSyntheticClass(clazz)) {
+      return ClassKind.NOT_SYNTHETIC;
+    }
+
+    // Do not allow merging synthetics that are not lambdas.
+    if (!syntheticItems.isNonLegacySynthetic(clazz)
+        || syntheticItems.getNonLegacySyntheticKind(clazz) != SyntheticKind.LAMBDA) {
+      return ineligibleForClassMerging();
+    }
+
+    // Allow merging Java lambdas with Java lambdas.
+    if (appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()) {
+      return ClassKind.SYNTHETIC;
+    }
+
+    // Java lambda merging is disabled.
+    return ineligibleForClassMerging();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index d0a86ef..2b63089 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -15,8 +15,10 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.BottomValue;
+import com.android.tools.r8.ir.analysis.value.NonConstantNumberValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -173,31 +175,65 @@
       while (iterator.hasNext()) {
         Map.Entry<DexEncodedField, AbstractValue> entry = iterator.next();
         DexEncodedField field = entry.getKey();
+        AbstractValue abstractValue = entry.getValue();
+
+        // The power set lattice is an expensive abstraction, so use it with caution.
+        boolean isClassIdField = HorizontalClassMergerUtils.isClassIdField(appView, field);
+
         InstanceFieldInitializationInfo initializationInfo =
             initializationInfoCollection.get(field);
         if (initializationInfo.isArgumentInitializationInfo()) {
           InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
               initializationInfo.asArgumentInitializationInfo();
           Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
-          AbstractValue abstractValue =
-              entry.getValue().join(argument.getAbstractValue(appView, context));
+          AbstractValue argumentAbstractValue = argument.getAbstractValue(appView, context);
+          abstractValue =
+              abstractValue.join(
+                  argumentAbstractValue,
+                  appView.abstractValueFactory(),
+                  field.getType(),
+                  isClassIdField);
           assert !abstractValue.isBottom();
-          if (!abstractValue.isUnknown()) {
-            entry.setValue(abstractValue);
-            continue;
-          }
         } else if (initializationInfo.isSingleValue()) {
           SingleValue singleValueInitializationInfo = initializationInfo.asSingleValue();
-          AbstractValue abstractValue = entry.getValue().join(singleValueInitializationInfo);
-          assert !abstractValue.isBottom();
-          if (!abstractValue.isUnknown()) {
-            entry.setValue(abstractValue);
-            continue;
-          }
+          abstractValue =
+              abstractValue.join(
+                  singleValueInitializationInfo,
+                  appView.abstractValueFactory(),
+                  field.getType(),
+                  isClassIdField);
         } else if (initializationInfo.isTypeInitializationInfo()) {
           // TODO(b/149732532): Not handled, for now.
+          abstractValue = UnknownValue.getInstance();
         } else {
           assert initializationInfo.isUnknown();
+          abstractValue = UnknownValue.getInstance();
+        }
+
+        assert !abstractValue.isBottom();
+
+        // When approximating the possible values for the $r8$classId fields from horizontal class
+        // merging, give up if the set of possible values equals the size of the merge group. In
+        // this case, the information is useless.
+        if (isClassIdField && abstractValue.isNonConstantNumberValue()) {
+          NonConstantNumberValue initialAbstractValue =
+              field.getOptimizationInfo().getAbstractValue().asNonConstantNumberValue();
+          if (initialAbstractValue != null) {
+            if (abstractValue.asNonConstantNumberValue().getAbstractionSize()
+                >= initialAbstractValue.getAbstractionSize()) {
+              abstractValue = UnknownValue.getInstance();
+            }
+          } else {
+            assert false
+                : "Expected abstract value of "
+                    + field.toSourceString()
+                    + " to be instance of NonConstantNumberValue";
+          }
+        }
+
+        if (!abstractValue.isUnknown()) {
+          entry.setValue(abstractValue);
+          continue;
         }
 
         // We just lost track for this field.
@@ -236,7 +272,11 @@
                 .fieldInitializationInfos()
                 .get(field);
         if (fieldInitializationInfo.isSingleValue()) {
-          abstractValue = abstractValue.join(fieldInitializationInfo.asSingleValue());
+          abstractValue =
+              abstractValue.join(
+                  fieldInitializationInfo.asSingleValue(),
+                  appView.abstractValueFactory(),
+                  field.getType());
           if (abstractValue.isUnknown()) {
             break;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 899fa2b..05259ca 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -40,6 +40,12 @@
 
 public class TrivialFieldAccessReprocessor {
 
+  enum FieldClassification {
+    CONSTANT,
+    NON_CONSTANT,
+    UNKNOWN
+  }
+
   private final AppView<AppInfoWithLiveness> appView;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
 
@@ -54,6 +60,9 @@
   private final Set<DexEncodedField> constantFields = Sets.newConcurrentHashSet();
 
   /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+  private final Set<DexEncodedField> nonConstantFields = Sets.newConcurrentHashSet();
+
+  /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
   private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
 
   public TrivialFieldAccessReprocessor(
@@ -72,7 +81,7 @@
     assert feedback.noUpdatesLeft();
 
     timing.begin("Compute fields of interest");
-    computeConstantFields();
+    computeFieldsWithNonTrivialValue();
     timing.end(); // Compute fields of interest
 
     timing.begin("Enqueue methods for reprocessing");
@@ -89,17 +98,32 @@
     writtenFields.keySet().forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
   }
 
-  private void computeConstantFields() {
+  private void computeFieldsWithNonTrivialValue() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedField field : clazz.instanceFields()) {
-        if (canOptimizeField(field, appView)) {
-          constantFields.add(field);
+        FieldClassification fieldClassification = classifyField(field, appView);
+        switch (fieldClassification) {
+          case CONSTANT:
+            // Reprocess reads and writes.
+            constantFields.add(field);
+            break;
+          case NON_CONSTANT:
+            // Only reprocess writes, to allow branch pruning.
+            nonConstantFields.add(field);
+            break;
+          default:
+            assert fieldClassification == FieldClassification.UNKNOWN;
+            break;
         }
       }
       if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) {
         for (DexEncodedField field : clazz.staticFields()) {
-          if (canOptimizeField(field, appView)) {
+          FieldClassification fieldClassification = classifyField(field, appView);
+          if (fieldClassification == FieldClassification.CONSTANT) {
             constantFields.add(field);
+          } else {
+            assert fieldClassification == FieldClassification.NON_CONSTANT
+                || fieldClassification == FieldClassification.UNKNOWN;
           }
         }
       }
@@ -138,30 +162,39 @@
         method -> method.registerCodeReferences(new TrivialFieldAccessUseRegistry(method)));
   }
 
-  private static boolean canOptimizeField(
+  private static FieldClassification classifyField(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
     FieldAccessInfo fieldAccessInfo =
         appView.appInfo().getFieldAccessInfoCollection().get(field.field);
-    if (fieldAccessInfo == null || fieldAccessInfo.isAccessedFromMethodHandle()) {
-      return false;
+    if (fieldAccessInfo == null
+        || fieldAccessInfo.hasReflectiveAccess()
+        || fieldAccessInfo.isAccessedFromMethodHandle()
+        || fieldAccessInfo.isReadFromAnnotation()) {
+      return FieldClassification.UNKNOWN;
     }
     AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
     if (abstractValue.isSingleValue()) {
       SingleValue singleValue = abstractValue.asSingleValue();
       if (!singleValue.isMaterializableInAllContexts(appView)) {
-        return false;
+        return FieldClassification.UNKNOWN;
       }
       if (singleValue.isSingleConstValue()) {
-        return true;
+        return FieldClassification.CONSTANT;
       }
       if (singleValue.isSingleFieldValue()) {
         SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
         DexField singleField = singleFieldValue.getField();
-        return singleField != field.field
-            && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView);
+        if (singleField != field.field
+            && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView)) {
+          return FieldClassification.CONSTANT;
+        }
       }
+      return FieldClassification.UNKNOWN;
     }
-    return false;
+    if (abstractValue.isNonConstantNumberValue()) {
+      return FieldClassification.NON_CONSTANT;
+    }
+    return FieldClassification.UNKNOWN;
   }
 
   private void processFieldsNeverRead(AppInfoWithLiveness appInfo) {
@@ -262,6 +295,13 @@
       DexClassAndField field = resolutionResult.getResolutionPair();
       DexEncodedField definition = field.getDefinition();
 
+      if (definition.isStatic() != isStatic
+          || appView.isCfByteCodePassThrough(method.getDefinition())
+          || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
+        recordAccessThatCannotBeOptimized(field, definition);
+        return;
+      }
+
       // Record access.
       if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
         if (field.getAccessFlags().isStatic() == isStatic) {
@@ -275,19 +315,17 @@
         }
       }
 
-      // We cannot remove references from pass through functions.
-      if (appView.isCfByteCodePassThrough(method.getDefinition())) {
-        constantFields.remove(definition);
-        return;
+      if (constantFields.contains(definition)
+          || (!isWrite && nonConstantFields.contains(definition))) {
+        methodsToReprocess.add(method);
       }
+    }
 
-      if (definition.isStatic() == isStatic) {
-        if (constantFields.contains(definition)) {
-          methodsToReprocess.add(method);
-        }
-      } else {
-        // Should generally not happen.
-        constantFields.remove(definition);
+    private void recordAccessThatCannotBeOptimized(
+        DexClassAndField field, DexEncodedField definition) {
+      constantFields.remove(definition);
+      if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
+        destroyFieldAccessContexts(definition);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java
rename to src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
index 73c9470..af7a836 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
@@ -11,7 +11,7 @@
  * A transfer function that defines the abstract semantics of the instructions in the program
  * according to some abstract state {@link StateType}.
  */
-public interface TransferFunction<StateType extends AbstractState<StateType>> {
+public interface AbstractTransferFunction<StateType extends AbstractState<StateType>> {
 
   TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
index 701df38..a8a1832 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -42,6 +42,14 @@
       this.blockExitStates = blockExitStates;
     }
 
+    public StateType join() {
+      StateType result = null;
+      for (StateType blockExitState : blockExitStates.values()) {
+        result = result != null ? result.join(blockExitState) : blockExitState;
+      }
+      return result;
+    }
+
     @Override
     public boolean isSuccessfulAnalysisResult() {
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
index 4341265..fd0a761 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
@@ -6,7 +6,10 @@
 
 import com.android.tools.r8.errors.Unreachable;
 
-/** Used by the {@link TransferFunction} to signal that the dataflow analysis should be aborted. */
+/**
+ * Used by the {@link AbstractTransferFunction} to signal that the dataflow analysis should be
+ * aborted.
+ */
 public class FailedTransferFunctionResult<StateType extends AbstractState<StateType>>
     implements TransferFunctionResult<StateType> {
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
index c990bdb..60136ce 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -16,11 +16,11 @@
  * This defines a simple fixpoint solver for running an intraprocedural dataflow analysis.
  *
  * <p>The solver computes an {@link AbstractState} for each {@link BasicBlock} using the {@link
- * TransferFunction} which defines the abstract semantics for each instruction.
+ * AbstractTransferFunction} which defines the abstract semantics for each instruction.
  *
  * <p>Once the fixpoint is reached the analysis returns a {@link SuccessfulDataflowAnalysisResult}.
- * If the supplied {@link TransferFunction} returns a {@link FailedTransferFunctionResult} for a
- * given instruction and abstract state, then the analysis return a {@link
+ * If the supplied {@link AbstractTransferFunction} returns a {@link FailedTransferFunctionResult}
+ * for a given instruction and abstract state, then the analysis return a {@link
  * FailedDataflowAnalysisResult}.
  */
 public class IntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>> {
@@ -28,12 +28,13 @@
   private final StateType bottom;
 
   // The transfer function that defines the abstract semantics for each instruction.
-  private final TransferFunction<StateType> transfer;
+  private final AbstractTransferFunction<StateType> transfer;
 
   // The state of the analysis.
   private final Map<BasicBlock, StateType> blockExitStates = new IdentityHashMap<>();
 
-  public IntraproceduralDataflowAnalysis(StateType bottom, TransferFunction<StateType> transfer) {
+  public IntraproceduralDataflowAnalysis(
+      StateType bottom, AbstractTransferFunction<StateType> transfer) {
     this.bottom = bottom;
     this.transfer = transfer;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
index 183d79a..96a17ab 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.ir.code.Instruction;
 
 /**
- * The result of applying the {@link TransferFunction} to an {@link Instruction} and an {@link
- * AbstractState}.
+ * The result of applying the {@link AbstractTransferFunction} to an {@link Instruction} and an
+ * {@link AbstractState}.
  *
  * <p>The result can either be a new {@link AbstractState} or a failure, in which case the dataflow
  * analysis is aborted.
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 d45bf07..a512bd4 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -52,6 +53,10 @@
     return false;
   }
 
+  public SingleConstValue asSingleConstValue() {
+    return null;
+  }
+
   public boolean isSingleConstClassValue() {
     return false;
   }
@@ -104,7 +109,47 @@
     return null;
   }
 
-  public AbstractValue join(AbstractValue other) {
+  public boolean isConstantOrNonConstantNumberValue() {
+    return false;
+  }
+
+  public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() {
+    return null;
+  }
+
+  public boolean isNonConstantNumberValue() {
+    return false;
+  }
+
+  public NonConstantNumberValue asNonConstantNumberValue() {
+    return null;
+  }
+
+  public boolean isNumberFromIntervalValue() {
+    return false;
+  }
+
+  public NumberFromIntervalValue asNumberFromIntervalValue() {
+    return null;
+  }
+
+  public boolean isNumberFromSetValue() {
+    return false;
+  }
+
+  public NumberFromSetValue asNumberFromSetValue() {
+    return null;
+  }
+
+  public AbstractValue join(AbstractValue other, AbstractValueFactory factory, DexType type) {
+    return join(other, factory, type, false);
+  }
+
+  public AbstractValue join(
+      AbstractValue other,
+      AbstractValueFactory factory,
+      DexType type,
+      boolean allowNonConstantNumbers) {
     if (isBottom() || other.isUnknown()) {
       return other;
     }
@@ -114,11 +159,31 @@
     if (equals(other)) {
       return this;
     }
-    if (isNull()) {
-      return NullOrAbstractValue.create(other);
+    if (type.isReferenceType()) {
+      if (isNull()) {
+        return NullOrAbstractValue.create(other);
+      }
+      if (other.isNull()) {
+        return NullOrAbstractValue.create(this);
+      }
     }
-    if (other.isNull()) {
-      return NullOrAbstractValue.create(this);
+    if (allowNonConstantNumbers
+        && isConstantOrNonConstantNumberValue()
+        && other.isConstantOrNonConstantNumberValue()) {
+      NumberFromSetValue.Builder numberFromSetValueBuilder;
+      if (isSingleNumberValue()) {
+        numberFromSetValueBuilder = NumberFromSetValue.builder(asSingleNumberValue());
+      } else {
+        assert isNumberFromSetValue();
+        numberFromSetValueBuilder = asNumberFromSetValue().instanceBuilder();
+      }
+      if (other.isSingleNumberValue()) {
+        numberFromSetValueBuilder.addInt(other.asSingleNumberValue().getIntValue());
+      } else {
+        assert other.isNumberFromSetValue();
+        numberFromSetValueBuilder.addInts(other.asNumberFromSetValue());
+      }
+      return numberFromSetValueBuilder.build(factory);
     }
     return UnknownValue.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java
new file mode 100644
index 0000000..4c6cf04
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ConstantOrNonConstantNumberValue.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.utils.OptionalBool;
+
+public interface ConstantOrNonConstantNumberValue {
+
+  boolean containsInt(int value);
+
+  OptionalBool isSubsetOf(int[] values);
+
+  ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue();
+
+  boolean isSingleNumberValue();
+
+  SingleNumberValue asSingleNumberValue();
+
+  boolean isNonConstantNumberValue();
+
+  NonConstantNumberValue asNonConstantNumberValue();
+
+  boolean isNumberFromIntervalValue();
+
+  NumberFromIntervalValue asNumberFromIntervalValue();
+
+  boolean isNumberFromSetValue();
+
+  NumberFromSetValue asNumberFromSetValue();
+
+  boolean mayOverlapWith(ConstantOrNonConstantNumberValue other);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java
new file mode 100644
index 0000000..e233568
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NonConstantNumberValue.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+public abstract class NonConstantNumberValue extends AbstractValue
+    implements ConstantOrNonConstantNumberValue {
+
+  @Override
+  public boolean isNonConstantNumberValue() {
+    return true;
+  }
+
+  @Override
+  public NonConstantNumberValue asNonConstantNumberValue() {
+    return this;
+  }
+
+  @Override
+  public boolean isConstantOrNonConstantNumberValue() {
+    return true;
+  }
+
+  @Override
+  public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() {
+    return this;
+  }
+
+  public abstract long getAbstractionSize();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
new file mode 100644
index 0000000..f100893
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.Objects;
+
+public class NumberFromIntervalValue extends NonConstantNumberValue {
+
+  private final long minInclusive;
+  private final long maxInclusive;
+
+  public NumberFromIntervalValue(long minInclusive, long maxInclusive) {
+    assert maxInclusive > minInclusive;
+    this.minInclusive = minInclusive;
+    this.maxInclusive = maxInclusive;
+  }
+
+  @Override
+  public boolean containsInt(int value) {
+    return minInclusive <= value && value <= maxInclusive;
+  }
+
+  @Override
+  public long getAbstractionSize() {
+    return maxInclusive - minInclusive + 1;
+  }
+
+  @Override
+  public boolean isNumberFromIntervalValue() {
+    return true;
+  }
+
+  @Override
+  public NumberFromIntervalValue asNumberFromIntervalValue() {
+    return this;
+  }
+
+  @Override
+  public boolean isNonTrivial() {
+    return true;
+  }
+
+  @Override
+  public OptionalBool isSubsetOf(int[] values) {
+    // Not implemented.
+    return OptionalBool.unknown();
+  }
+
+  @Override
+  public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+    if (other.isSingleNumberValue()) {
+      return containsInt(other.asSingleNumberValue().getIntValue());
+    }
+    if (other.isNumberFromIntervalValue()) {
+      return mayOverlapWith(other.asNumberFromIntervalValue());
+    }
+    assert other.isNumberFromSetValue();
+    return mayOverlapWith(other.asNumberFromSetValue());
+  }
+
+  public boolean mayOverlapWith(NumberFromIntervalValue other) {
+    return minInclusive <= other.maxInclusive && maxInclusive >= other.minInclusive;
+  }
+
+  public boolean mayOverlapWith(NumberFromSetValue other) {
+    return other.mayOverlapWith(this);
+  }
+
+  @Override
+  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == null || o.getClass() != getClass()) {
+      return false;
+    }
+    NumberFromIntervalValue numberFromIntervalValue = (NumberFromIntervalValue) o;
+    return minInclusive == numberFromIntervalValue.minInclusive
+        && maxInclusive == numberFromIntervalValue.maxInclusive;
+  }
+
+  @Override
+  public int hashCode() {
+    int hash = 31 * (31 * (31 + Long.hashCode(minInclusive)) + Long.hashCode(maxInclusive));
+    assert hash == Objects.hash(minInclusive, maxInclusive);
+    return hash;
+  }
+
+  @Override
+  public String toString() {
+    return "NumberFromIntervalValue([" + minInclusive + "; " + maxInclusive + "])";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
new file mode 100644
index 0000000..e6062f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromSetValue.java
@@ -0,0 +1,163 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.OptionalBool;
+import it.unimi.dsi.fastutil.ints.IntArraySet;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import java.util.Arrays;
+
+public class NumberFromSetValue extends NonConstantNumberValue {
+
+  private static final int MAX_SIZE = 30;
+
+  private final IntSet numbers;
+
+  private NumberFromSetValue(IntSet numbers) {
+    this.numbers = numbers;
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  static Builder builder(SingleNumberValue singleNumberValue) {
+    return new Builder().addInt(singleNumberValue.getIntValue());
+  }
+
+  Builder instanceBuilder() {
+    return new Builder(this);
+  }
+
+  @Override
+  public boolean containsInt(int value) {
+    return numbers.contains(value);
+  }
+
+  @Override
+  public long getAbstractionSize() {
+    return numbers.size();
+  }
+
+  @Override
+  public boolean isNumberFromSetValue() {
+    return true;
+  }
+
+  @Override
+  public NumberFromSetValue asNumberFromSetValue() {
+    return this;
+  }
+
+  @Override
+  public boolean isNonTrivial() {
+    return true;
+  }
+
+  @Override
+  public OptionalBool isSubsetOf(int[] values) {
+    assert ArrayUtils.isSorted(values);
+    for (int number : numbers) {
+      if (Arrays.binarySearch(values, number) < 0) {
+        return OptionalBool.FALSE;
+      }
+    }
+    return OptionalBool.TRUE;
+  }
+
+  @Override
+  public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+    if (other.isSingleNumberValue()) {
+      return containsInt(other.asSingleNumberValue().getIntValue());
+    }
+    assert other.isNonConstantNumberValue();
+    for (int number : numbers) {
+      if (other.containsInt(number)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public AbstractValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == null || o.getClass() != getClass()) {
+      return false;
+    }
+    NumberFromSetValue numberFromSetValue = (NumberFromSetValue) o;
+    return numbers.equals(numberFromSetValue.numbers);
+  }
+
+  @Override
+  public int hashCode() {
+    return numbers.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder("NumberFromSetValue(");
+    IntIterator iterator = numbers.iterator();
+    builder.append(iterator.nextInt());
+    while (iterator.hasNext()) {
+      builder.append(", ").append(iterator.nextInt());
+    }
+    return builder.append(")").toString();
+  }
+
+  static class Builder {
+
+    private IntSet numbers;
+
+    Builder() {
+      numbers = new IntArraySet();
+    }
+
+    Builder(NumberFromSetValue numberFromSetValue) {
+      numbers = new IntArraySet(numberFromSetValue.numbers);
+    }
+
+    Builder addInt(int number) {
+      if (numbers != null) {
+        assert numbers.size() <= MAX_SIZE;
+        if (numbers.add(number) && numbers.size() > MAX_SIZE) {
+          numbers = null;
+        }
+      }
+      return this;
+    }
+
+    Builder addInts(NumberFromSetValue numberFromSetValue) {
+      if (numbers != null) {
+        assert numbers.size() <= MAX_SIZE;
+        if (numbers.addAll(numberFromSetValue.numbers) && numbers.size() > MAX_SIZE) {
+          numbers = null;
+        }
+      }
+      return this;
+    }
+
+    AbstractValue build(AbstractValueFactory abstractValueFactory) {
+      if (numbers != null) {
+        assert !numbers.isEmpty();
+        assert numbers.size() <= MAX_SIZE;
+        if (numbers.size() == 1) {
+          return abstractValueFactory.createSingleNumberValue(numbers.iterator().nextInt());
+        }
+        return new NumberFromSetValue(numbers);
+      }
+      return UnknownValue.getInstance();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
index f0af9b4..4db63a6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -8,10 +8,12 @@
 import com.android.tools.r8.graph.DexEncodedField;
 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.shaking.AppInfoWithLiveness;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.function.BiConsumer;
+import java.util.function.Predicate;
 
 public abstract class ObjectState {
 
@@ -25,6 +27,22 @@
 
   public abstract void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer);
 
+  public final boolean hasMaterializableFieldValueThatMatches(
+      AppView<AppInfoWithLiveness> appView,
+      DexEncodedField field,
+      ProgramMethod context,
+      Predicate<SingleValue> predicate) {
+    AbstractValue abstractValue = getAbstractFieldValue(field);
+    if (!abstractValue.isSingleValue()) {
+      return false;
+    }
+    SingleValue singleValue = abstractValue.asSingleValue();
+    if (!singleValue.isMaterializableInContext(appView, context)) {
+      return false;
+    }
+    return predicate.test(singleValue);
+  }
+
   public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
 
   public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
index ecf9838..11d238a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstValue.java
@@ -10,4 +10,9 @@
   public boolean isSingleConstValue() {
     return true;
   }
+
+  @Override
+  public SingleConstValue asSingleConstValue() {
+    return this;
+  }
 }
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 76e6ac8..d51b94a 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
@@ -16,8 +16,11 @@
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.OptionalBool;
 
-public class SingleNumberValue extends SingleConstValue {
+public class SingleNumberValue extends SingleConstValue
+    implements ConstantOrNonConstantNumberValue {
 
   private final long value;
 
@@ -27,6 +30,16 @@
   }
 
   @Override
+  public boolean containsInt(int value) {
+    return value == getIntValue();
+  }
+
+  @Override
+  public OptionalBool isSubsetOf(int[] values) {
+    return OptionalBool.of(ArrayUtils.containsInt(values, getIntValue()));
+  }
+
+  @Override
   public boolean isSingleBoolean() {
     return isFalse() || isTrue();
   }
@@ -51,6 +64,16 @@
     return this;
   }
 
+  @Override
+  public boolean isConstantOrNonConstantNumberValue() {
+    return true;
+  }
+
+  @Override
+  public ConstantOrNonConstantNumberValue asConstantOrNonConstantNumberValue() {
+    return this;
+  }
+
   public boolean getBooleanValue() {
     assert value == 0 || value == 1;
     return value != 0;
@@ -77,6 +100,15 @@
   }
 
   @Override
+  public boolean mayOverlapWith(ConstantOrNonConstantNumberValue other) {
+    if (other.isSingleNumberValue()) {
+      return equals(other.asSingleNumberValue());
+    }
+    assert other.isNonConstantNumberValue();
+    return other.asNonConstantNumberValue().containsInt(getIntValue());
+  }
+
+  @Override
   public boolean equals(Object o) {
     return this == o;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index cbc8c6b..506e8af 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1096,6 +1096,14 @@
     return arguments;
   }
 
+  public Argument getLastArgument() {
+    InstructionIterator instructionIterator = entryBlock().iterator(getNumberOfArguments() - 1);
+    Argument lastArgument = instructionIterator.next().asArgument();
+    assert lastArgument != null;
+    assert !instructionIterator.peekNext().isArgument();
+    return lastArgument;
+  }
+
   public Value getThis() {
     if (method().accessFlags.isStatic()) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 689514f..4c1d942 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -309,7 +309,11 @@
   }
 
   public void replace(Instruction newInstruction, IRCode code) {
-    getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction);
+    replace(newInstruction, code, null);
+  }
+
+  public void replace(Instruction newInstruction, IRCode code, Set<Value> affectedValues) {
+    getBlock().listIterator(code, this).replaceCurrentInstruction(newInstruction, affectedValues);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index fdaf8f0..94ab740 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Iterables;
 import java.util.List;
 
 public abstract class InvokeMethodWithReceiver extends InvokeMethod {
@@ -32,6 +33,10 @@
     super(target, result, arguments);
   }
 
+  public Iterable<Value> getNonReceiverArguments() {
+    return Iterables.skip(arguments(), 1);
+  }
+
   public boolean hasRefinedReceiverLowerBoundType(AppView<AppInfoWithLiveness> appView) {
     assert isInvokeMethodWithDynamicDispatch();
     return getReceiver().getDynamicLowerBoundType(appView) != null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e0841f7..8a63d63 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -164,7 +164,7 @@
     DexBuilder.removeRedundantDebugPositions(code);
     CfCode code = buildCfCode();
     assert verifyInvokeInterface(code, appView);
-    assert code.verifyFrames(method, appView, this.code.origin, false);
+    assert code.verifyFrames(method, appView, this.code.origin, false).isValid();
     return code;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 50ba68b..1d7f619 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -671,7 +671,7 @@
   public DexType getPhiTypeForBlock(
       int register, int blockOffset, ValueTypeConstraint constraint, RegisterReadType readType) {
     assert code.getStackMapStatus() != StackMapStatus.NOT_VERIFIED;
-    if (code.getStackMapStatus() == StackMapStatus.INVALID_OR_NOT_PRESENT) {
+    if (code.getStackMapStatus().isInvalidOrNotPresent()) {
       return null;
     }
     // We should be able to find the a snapshot at the block-offset:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 6d79c9c..8f83dca 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -50,6 +52,11 @@
       ClassConverterResult.Builder resultBuilder, ExecutorService executorService)
       throws ExecutionException {
     List<DexProgramClass> classes = appView.appInfo().classes();
+
+    D8CfClassDesugaringEventConsumer classDesugaringEventConsumer =
+        CfClassDesugaringEventConsumer.createForD8(methodProcessor);
+    converter.desugarClassesForD8(classes, classDesugaringEventConsumer, executorService);
+
     while (!classes.isEmpty()) {
       Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
       List<DexProgramClass> deferred = new ArrayList<>(classes.size() / 2);
@@ -65,18 +72,19 @@
         }
       }
 
-      // Process the wave and wait for all IR processing to complete.
-      D8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
+      D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer =
           CfInstructionDesugaringEventConsumer.createForD8(methodProcessor);
+
+      // Process the wave and wait for all IR processing to complete.
       methodProcessor.newWave();
       ThreadUtils.processItems(
-          wave, clazz -> convertClass(clazz, desugaringEventConsumer), executorService);
+          wave, clazz -> convertClass(clazz, instructionDesugaringEventConsumer), executorService);
       methodProcessor.awaitMethodProcessing();
 
       // Finalize the desugaring of the processed classes. This may require processing (and
       // reprocessing) of some methods.
       List<ProgramMethod> needsProcessing =
-          desugaringEventConsumer.finalizeDesugaring(appView, resultBuilder);
+          instructionDesugaringEventConsumer.finalizeDesugaring(appView, resultBuilder);
       if (!needsProcessing.isEmpty()) {
         // Create a new processor context to ensure unique method processing contexts.
         methodProcessor.newWave();
@@ -90,13 +98,13 @@
               if (definition.isProcessed()) {
                 definition.markNotProcessed();
               }
-              methodProcessor.processMethod(method, desugaringEventConsumer);
+              methodProcessor.processMethod(method, instructionDesugaringEventConsumer);
             },
             executorService);
 
         // Verify there is nothing to finalize once method processing finishes.
         methodProcessor.awaitMethodProcessing();
-        assert desugaringEventConsumer.verifyNothingToFinalize();
+        assert instructionDesugaringEventConsumer.verifyNothingToFinalize();
       }
 
       classes = deferred;
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 72f9ac7..54b0a66 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
@@ -42,6 +42,8 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
@@ -125,7 +127,8 @@
   private final Timing timing;
   private final Outliner outliner;
   private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization;
-  private final CfInstructionDesugaringCollection desugaring;
+  private final CfClassDesugaringCollection classDesugaring;
+  private final CfInstructionDesugaringCollection instructionDesugaring;
   private final FieldAccessAnalysis fieldAccessAnalysis;
   private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
   private final StringOptimizer stringOptimizer;
@@ -217,7 +220,8 @@
       // - nest based access desugaring,
       // - invoke-special desugaring.
       assert options.desugarState.isOn();
-      this.desugaring = CfInstructionDesugaringCollection.create(appView);
+      this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
+      this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
       this.desugaredLibraryRetargeter =
           options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
               ? null
@@ -249,10 +253,11 @@
       this.assumeInserter = null;
       return;
     }
-    this.desugaring =
+    this.instructionDesugaring =
         appView.enableWholeProgramOptimizations()
             ? CfInstructionDesugaringCollection.empty()
             : CfInstructionDesugaringCollection.create(appView);
+    this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
     this.interfaceMethodRewriter =
         options.isInterfaceMethodDesugaringEnabled()
             ? new InterfaceMethodRewriter(appView, this)
@@ -349,7 +354,7 @@
   private void synthesizeBridgesForNestBasedAccessesOnClasspath(
       D8MethodProcessor methodProcessor, ExecutorService executorService)
       throws ExecutionException {
-    desugaring.withD8NestBasedAccessDesugaring(
+    instructionDesugaring.withD8NestBasedAccessDesugaring(
         d8NestBasedAccessDesugaring ->
             d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
                 methodProcessor, executorService));
@@ -357,14 +362,15 @@
   }
 
   private void reportNestDesugarDependencies() {
-    desugaring.withD8NestBasedAccessDesugaring(
+    instructionDesugaring.withD8NestBasedAccessDesugaring(
         D8NestBasedAccessDesugaring::reportDesugarDependencies);
   }
 
-  private void staticizeClasses(OptimizationFeedback feedback, ExecutorService executorService)
+  private void staticizeClasses(
+      OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
       throws ExecutionException {
     if (classStaticizer != null) {
-      classStaticizer.staticizeCandidates(feedback, executorService);
+      classStaticizer.staticizeCandidates(feedback, executorService, applied);
     }
   }
 
@@ -459,6 +465,26 @@
         appView, classConverterResult.getForcefullyMovedLambdaMethods());
   }
 
+  public void desugarClassesForD8(
+      List<DexProgramClass> classes,
+      D8CfClassDesugaringEventConsumer desugaringEventConsumer,
+      ExecutorService executorService)
+      throws ExecutionException {
+    if (classDesugaring.isEmpty()) {
+      return;
+    }
+    // Currently the classes can be processed in any order and do not require to be sorted.
+    ThreadUtils.processItems(
+        classes, clazz -> desugarClassForD8(clazz, desugaringEventConsumer), executorService);
+  }
+
+  public void desugarClassForD8(
+      DexProgramClass clazz, D8CfClassDesugaringEventConsumer desugaringEventConsumer) {
+    if (classDesugaring.needsDesugaring(clazz)) {
+      classDesugaring.desugar(clazz, desugaringEventConsumer);
+    }
+  }
+
   void convertMethods(
       DexProgramClass clazz,
       D8CfInstructionDesugaringEventConsumer desugaringEventConsumer,
@@ -544,7 +570,7 @@
     if (!options.cfToCfDesugar) {
       return true;
     }
-    if (desugaring.needsDesugaring(method)) {
+    if (instructionDesugaring.needsDesugaring(method)) {
       return true;
     }
     if (desugaredLibraryAPIConverter != null
@@ -620,7 +646,7 @@
       AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
       throws ExecutionException {
     // Desugaring happens in the enqueuer.
-    assert desugaring.isEmpty();
+    assert instructionDesugaring.isEmpty();
 
     DexApplication application = appView.appInfo().app();
 
@@ -723,7 +749,7 @@
     if (!options.isGeneratingClassFiles()) {
       printPhase("Class staticizer post processing");
       // TODO(b/127694949): Adapt to PostOptimization.
-      staticizeClasses(feedback, executorService);
+      staticizeClasses(feedback, executorService, initialGraphLensForIR);
       feedback.updateVisibleOptimizationInfo();
       // The class staticizer lens shall not be applied through lens code rewriting or it breaks
       // the lambda merger.
@@ -1109,13 +1135,15 @@
       ProgramMethod method,
       CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       MethodProcessingContext methodProcessingContext) {
-    if (options.desugarState.isOff()
-        || !method.getDefinition().getCode().isCfCode()
-        || !desugaring.needsDesugaring(method)) {
+    if (options.desugarState.isOff() || !method.getDefinition().getCode().isCfCode()) {
       return false;
     }
-    desugaring.desugar(method, methodProcessingContext, desugaringEventConsumer);
-    return true;
+    instructionDesugaring.scan(method, desugaringEventConsumer);
+    if (instructionDesugaring.needsDesugaring(method)) {
+      instructionDesugaring.desugar(method, methodProcessingContext, desugaringEventConsumer);
+      return true;
+    }
+    return false;
   }
 
   // TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
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 0cdf967..9dfe2e3 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
@@ -13,9 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -64,15 +62,11 @@
   void setClassInlinerMethodConstraint(
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint);
 
-  void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility);
-
   void setInstanceInitializerInfoCollection(
       DexEncodedMethod method, InstanceInitializerInfoCollection instanceInitializerInfoCollection);
 
   void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method);
 
-  void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
-
   void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts);
 
   void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts);
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 bd5c0b3..5cd32f2 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
@@ -78,7 +78,8 @@
     CfInvoke invoke = instruction.asInvoke();
     MethodProvider methodProvider = getMethodProviderOrNull(invoke.getMethod());
     return methodProvider != null
-        ? methodProvider.rewriteInvoke(invoke, appView, eventConsumer, methodProcessingContext)
+        ? methodProvider.rewriteInvoke(
+            invoke, appView, eventConsumer, methodProcessingContext, localStackAllocator)
         : null;
   }
 
@@ -194,6 +195,7 @@
       }
 
       // These are currently not implemented at any API level in Android.
+      initializeJava9MethodProviders(factory);
       initializeJava10MethodProviders(factory);
       initializeJava11MethodProviders(factory);
     }
@@ -1043,6 +1045,60 @@
       }
     }
 
+    private void initializeJava9MethodProviders(DexItemFactory factory) {
+      // Integer
+      DexType type = factory.boxedIntType;
+      // long Long.parseLong(CharSequence s, int beginIndex, int endIndex, int radix)
+      DexString name = factory.createString("parseInt");
+      DexProto proto =
+          factory.createProto(
+              factory.intType,
+              factory.charSequenceType,
+              factory.intType,
+              factory.intType,
+              factory.intType);
+      DexMethod method = factory.createMethod(type, proto, name);
+      addProvider(
+          new MethodGenerator(
+              method,
+              BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadix,
+              "parseIntSubsequenceWithRadix"));
+
+      // Long
+      type = factory.boxedLongType;
+      // long Long.parseLong(CharSequence s, int beginIndex, int endIndex, int radix)
+      name = factory.createString("parseLong");
+      proto =
+          factory.createProto(
+              factory.longType,
+              factory.charSequenceType,
+              factory.intType,
+              factory.intType,
+              factory.intType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new MethodGenerator(
+              method,
+              BackportedMethods::LongMethods_parseLongSubsequenceWithRadix,
+              "parseLongSubsequenceWithRadix"));
+
+      // long Long.parseUnsignedLong(CharSequence s, int beginIndex, int endIndex, int radix)
+      name = factory.createString("parseUnsignedLong");
+      proto =
+          factory.createProto(
+              factory.longType,
+              factory.charSequenceType,
+              factory.intType,
+              factory.intType,
+              factory.intType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(
+          new MethodGenerator(
+              method,
+              BackportedMethods::LongMethods_parseUnsignedLongSubsequenceWithRadix,
+              "parseUnsignedLongSubsequenceWithRadix"));
+    }
+
     private void initializeJava10MethodProviders(DexItemFactory factory) {
       // List
       DexType type = factory.listType;
@@ -1309,7 +1365,8 @@
         CfInvoke invoke,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
-        MethodProcessingContext methodProcessingContext);
+        MethodProcessingContext methodProcessingContext,
+        LocalStackAllocator localStackAllocator);
   }
 
   private static final class InvokeRewriter extends MethodProvider {
@@ -1326,8 +1383,9 @@
         CfInvoke invoke,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
-        MethodProcessingContext methodProcessingContext) {
-      return rewriter.rewrite(invoke, appView.dexItemFactory());
+        MethodProcessingContext methodProcessingContext,
+        LocalStackAllocator localStackAllocator) {
+      return rewriter.rewrite(invoke, appView.dexItemFactory(), localStackAllocator);
     }
   }
 
@@ -1351,7 +1409,8 @@
         CfInvoke invoke,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
-        MethodProcessingContext methodProcessingContext) {
+        MethodProcessingContext methodProcessingContext,
+        LocalStackAllocator localStackAllocator) {
       ProgramMethod method = getSyntheticMethod(appView, methodProcessingContext);
       eventConsumer.acceptBackportedMethod(method, methodProcessingContext.getMethodContext());
       return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
@@ -1410,7 +1469,8 @@
     CfInstruction rewriteSingle(CfInvoke invoke, DexItemFactory factory);
 
     // Convenience wrapper since most rewrites are to a single instruction.
-    default Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) {
+    default Collection<CfInstruction> rewrite(
+        CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) {
       return ImmutableList.of(rewriteSingle(invoke, factory));
     }
   }
@@ -1423,6 +1483,7 @@
     }
 
     @Override
-    public abstract Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory);
+    public abstract Collection<CfInstruction> rewrite(
+        CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java
new file mode 100644
index 0000000..a885344
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaring.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+/** Interface for desugaring a class. */
+public interface CfClassDesugaring {
+
+  void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer);
+
+  /** Returns true if the given class needs desugaring. */
+  boolean needsDesugaring(DexProgramClass clazz);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
new file mode 100644
index 0000000..9b7c709
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+/** Interface for desugaring a class. */
+public abstract class CfClassDesugaringCollection {
+
+  public abstract void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer);
+
+  /** Returns true if the given class needs desugaring. */
+  public abstract boolean needsDesugaring(DexProgramClass clazz);
+
+  public abstract boolean isEmpty();
+
+  public static class NonEmptyCfClassDesugaringCollection extends CfClassDesugaringCollection {
+    private final RecordRewriter recordRewriter;
+
+    NonEmptyCfClassDesugaringCollection(RecordRewriter recordRewriter) {
+      this.recordRewriter = recordRewriter;
+    }
+
+    @Override
+    public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
+      recordRewriter.desugar(clazz, eventConsumer);
+    }
+
+    @Override
+    public boolean needsDesugaring(DexProgramClass clazz) {
+      return recordRewriter.needsDesugaring(clazz);
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+  }
+
+  public static class EmptyCfClassDesugaringCollection extends CfClassDesugaringCollection {
+    @Override
+    public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public boolean needsDesugaring(DexProgramClass clazz) {
+      return false;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return true;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
new file mode 100644
index 0000000..d5acaa7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+
+public abstract class CfClassDesugaringEventConsumer implements RecordDesugaringEventConsumer {
+
+  public static D8CfClassDesugaringEventConsumer createForD8(D8MethodProcessor methodProcessor) {
+    return new D8CfClassDesugaringEventConsumer(methodProcessor);
+  }
+
+  public static class D8CfClassDesugaringEventConsumer extends CfClassDesugaringEventConsumer {
+
+    private final D8MethodProcessor methodProcessor;
+
+    public D8CfClassDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
+      this.methodProcessor = methodProcessor;
+    }
+
+    @Override
+    public void acceptRecordClass(DexProgramClass recordClass) {
+      methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
+    }
+  }
+
+  // TODO(b/): Implement R8CfClassDesugaringEventConsumer
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 21ac7ae..3fb9329 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 /**
  * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -34,6 +35,9 @@
     return EmptyCfInstructionDesugaringCollection.getInstance();
   }
 
+  public abstract void scan(
+      ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer);
+
   /** Desugars the instructions in the given method. */
   public abstract void desugar(
       ProgramMethod method,
@@ -44,9 +48,13 @@
     return false;
   }
 
+  public abstract CfClassDesugaringCollection createClassDesugaringCollection();
+
   /** Returns true if the given method needs desugaring. */
   public abstract boolean needsDesugaring(ProgramMethod method);
 
   public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
       ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
+
+  public abstract void withRecordRewriter(Consumer<RecordRewriter> consumer);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index d765537..a95569e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -39,6 +39,7 @@
         InvokeSpecialToSelfDesugaringEventConsumer,
         LambdaDesugaringEventConsumer,
         NestBasedAccessDesugaringEventConsumer,
+        RecordDesugaringEventConsumer,
         TwrCloseResourceDesugaringEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
@@ -58,6 +59,11 @@
     return new CfInstructionDesugaringEventConsumer() {
 
       @Override
+      public void acceptRecordClass(DexProgramClass recordClass) {
+        assert false;
+      }
+
+      @Override
       public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
         assert false;
       }
@@ -121,6 +127,11 @@
     }
 
     @Override
+    public void acceptRecordClass(DexProgramClass recordClass) {
+      methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
+    }
+
+    @Override
     public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
       synchronized (synthesizedLambdaClasses) {
         synthesizedLambdaClasses.add(lambdaClass);
@@ -225,6 +236,12 @@
     }
 
     @Override
+    public void acceptRecordClass(DexProgramClass recordClass) {
+      // This is called each time an instruction or a class is found to require the record class.
+      assert false : "TODO(b/179146128): To be implemented";
+    }
+
+    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
       // present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 6dbd525..8a6624e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -256,7 +256,7 @@
                 : new DexEncodedMethod[] {uniqueMethod},
             factory.getSkipNameValidationForTesting(),
             getChecksumSupplier(uniqueMethod, appView));
-    appView.appInfo().addSynthesizedClass(newClass, false);
+    appView.appInfo().addSynthesizedClassForLibraryDesugaring(newClass);
     builder.addSynthesizedClass(newClass);
   }
 
@@ -409,6 +409,21 @@
             itemFactory.createMethod(
                 itemFactory.createType("Ljava/util/DesugarArrays;"), proto, name);
         retargetLibraryMember.put(source, target);
+
+        // TODO(b/181629049): This is only a workaround rewriting invokes of
+        //  j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
+        // to j.u.DesugarArrays.deepEquals0.
+        name = itemFactory.createString("getTimeZone");
+        proto =
+            itemFactory.createProto(
+                itemFactory.createType("Ljava/util/TimeZone;"),
+                itemFactory.createType("Ljava/time/ZoneId;"));
+        source =
+            itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
+        target =
+            itemFactory.createMethod(
+                itemFactory.createType("Ljava/util/DesugarTimeZone;"), proto, name);
+        retargetLibraryMember.put(source, target);
       }
     }
 
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 eddbf6d..ce4f61a 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
@@ -537,7 +537,7 @@
       throws ExecutionException {
     for (DexProgramClass wrapper : wrappers) {
       builder.addSynthesizedClass(wrapper);
-      appView.appInfo().addSynthesizedClass(wrapper, false);
+      appView.appInfo().addSynthesizedClassForLibraryDesugaring(wrapper);
     }
     irConverter.optimizeSynthesizedClasses(wrappers, executorService);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 60b3537..c8b6147 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -6,8 +6,10 @@
 
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -22,6 +24,11 @@
   }
 
   @Override
+  public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+    // Intentionally empty.
+  }
+
+  @Override
   public void desugar(
       ProgramMethod method,
       MethodProcessingContext methodProcessingContext,
@@ -35,6 +42,11 @@
   }
 
   @Override
+  public CfClassDesugaringCollection createClassDesugaringCollection() {
+    return new EmptyCfClassDesugaringCollection();
+  }
+
+  @Override
   public boolean needsDesugaring(ProgramMethod method) {
     return false;
   }
@@ -44,4 +56,9 @@
       ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) {
     // Intentionally empty.
   }
+
+  @Override
+  public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
+    // Intentionally empty.
+  }
 }
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 7d041f6..a1c0d58 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
@@ -979,8 +979,7 @@
             emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
             DexEncodedMethod.EMPTY_ARRAY,
             factory.getSkipNameValidationForTesting(),
-            DexProgramClass::checksumFromType,
-            Collections.singletonList(theInterface));
+            DexProgramClass::checksumFromType);
     clazz.forEachProgramMethod(synthesizedMethods::add);
     return clazz;
   }
@@ -1467,7 +1466,7 @@
     // At this point we likely have a non-library type that may depend on default method information
     // from its interfaces and the dependency should be reported.
     if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) {
-      reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.options());
+      reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.appInfo());
     }
 
     // Merge information from all superinterfaces.
@@ -1491,19 +1490,19 @@
   }
 
   public static void reportDependencyEdge(
-      DexClass dependent, DexClass dependency, InternalOptions options) {
+      DexClass dependent, DexClass dependency, AppInfo appInfo) {
     assert !dependent.isLibraryClass();
     assert !dependency.isLibraryClass();
-    DesugarGraphConsumer consumer = options.desugarGraphConsumer;
+    DesugarGraphConsumer consumer = appInfo.app().options.desugarGraphConsumer;
     if (consumer != null) {
       Origin dependencyOrigin = dependency.getOrigin();
-      java.util.Collection<DexProgramClass> dependents =
-          dependent.isProgramClass() ? dependent.asProgramClass().getSynthesizedFrom() : null;
-      if (dependents == null || dependents.isEmpty()) {
+      java.util.Collection<DexType> dependents =
+          appInfo.getSyntheticItems().getSynthesizingContexts(dependent.getType());
+      if (dependents.isEmpty()) {
         reportDependencyEdge(consumer, dependencyOrigin, dependent);
       } else {
-        for (DexClass clazz : dependents) {
-          reportDependencyEdge(consumer, dependencyOrigin, clazz);
+        for (DexType type : dependents) {
+          reportDependencyEdge(consumer, dependencyOrigin, appInfo.definitionFor(type));
         }
       }
     }
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 e40251c..d20eee7 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
@@ -135,8 +135,7 @@
             companionMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
             DexEncodedMethod.EMPTY_ARRAY,
             rewriter.factory.getSkipNameValidationForTesting(),
-            getChecksumSupplier(iface),
-            Collections.singletonList(iface));
+            getChecksumSupplier(iface));
     syntheticClasses.put(iface, companionClass);
     if (companionClass.hasClassInitializer()) {
       newSynthesizedMethodConsumer.accept(companionClass.getProgramClassInitializer());
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 076c0c4..d54264c 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
@@ -230,16 +230,17 @@
     int fieldCount = fieldTypes.length;
     List<DexEncodedField> fields = new ArrayList<>(fieldCount);
     for (int i = 0; i < fieldCount; i++) {
-      FieldAccessFlags accessFlags =
-          FieldAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
+      boolean deprecated = false;
+      boolean d8R8Synthesized = true;
       fields.add(
           new DexEncodedField(
               getCaptureField(i),
-              accessFlags,
+              FieldAccessFlags.createPublicFinalSynthetic(),
               FieldTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
-              null));
+              null,
+              deprecated,
+              d8R8Synthesized));
     }
     builder.setInstanceFields(fields);
   }
@@ -249,6 +250,8 @@
     if (isStateless()) {
       // Create instance field for stateless lambda.
       assert this.lambdaField != null;
+      boolean deprecated = false;
+      boolean d8R8Synthesized = true;
       builder.setStaticFields(
           Collections.singletonList(
               new DexEncodedField(
@@ -260,7 +263,9 @@
                           | Constants.ACC_STATIC),
                   FieldTypeSignature.noSignature(),
                   DexAnnotationSet.empty(),
-                  DexValueNull.NULL)));
+                  DexValueNull.NULL,
+                  deprecated,
+                  d8R8Synthesized)));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index cbab081..6975252 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -361,9 +361,18 @@
       Builder<CfInstruction> instructions,
       DexItemFactory factory) {
     internalAdjustType(fromType, toType, returnType, instructions, factory);
+    if (fromType == toType) {
+      return ValueType.fromDexType(fromType).requiredRegisters();
+    }
+    // Account for the potential unboxing of a wide type.
+    DexType fromTypeAsPrimitive = factory.getPrimitiveFromBoxed(fromType);
     return Math.max(
         ValueType.fromDexType(fromType).requiredRegisters(),
-        ValueType.fromDexType(toType).requiredRegisters());
+        Math.max(
+            fromTypeAsPrimitive == null
+                ? 0
+                : ValueType.fromDexType(fromTypeAsPrimitive).requiredRegisters(),
+            ValueType.fromDexType(toType).requiredRegisters()));
   }
 
   private static void internalAdjustType(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 5815053..3a7a195 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
@@ -28,6 +30,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -35,6 +38,7 @@
   private final List<CfInstructionDesugaring> desugarings = new ArrayList<>();
 
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
+  private final RecordRewriter recordRewriter;
 
   NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
     this.appView = appView;
@@ -54,6 +58,11 @@
     if (nestBasedAccessDesugaring != null) {
       desugarings.add(nestBasedAccessDesugaring);
     }
+    this.recordRewriter = RecordRewriter.create(appView);
+    if (recordRewriter != null) {
+      assert !appView.enableWholeProgramOptimizations() : "To be implemented";
+      desugarings.add(recordRewriter);
+    }
   }
 
   // TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled.
@@ -62,6 +71,7 @@
       AppView<?> appView, InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring) {
     this.appView = appView;
     this.nestBasedAccessDesugaring = null;
+    this.recordRewriter = null;
     desugarings.add(invokeSpecialToSelfDesugaring);
   }
 
@@ -72,13 +82,8 @@
         appView, new InvokeSpecialToSelfDesugaring(appView));
   }
 
-  @Override
-  public void desugar(
-      ProgramMethod method,
-      MethodProcessingContext methodProcessingContext,
-      CfInstructionDesugaringEventConsumer eventConsumer) {
-    Code code = method.getDefinition().getCode();
-    if (!code.isCfCode()) {
+  private void ensureCfCode(ProgramMethod method) {
+    if (!method.getDefinition().getCode().isCfCode()) {
       appView
           .options()
           .reporter
@@ -87,10 +92,24 @@
                   "Unsupported attempt to desugar non-CF code",
                   method.getOrigin(),
                   method.getPosition()));
-      return;
     }
+  }
 
-    CfCode cfCode = code.asCfCode();
+  @Override
+  public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+    ensureCfCode(method);
+    if (recordRewriter != null) {
+      recordRewriter.scan(method, eventConsumer);
+    }
+  }
+
+  @Override
+  public void desugar(
+      ProgramMethod method,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    ensureCfCode(method);
+    CfCode cfCode = method.getDefinition().getCode().asCfCode();
 
     // Tracking of temporary locals used for instruction desugaring. The desugaring of each
     // instruction is assumed to use locals only for the duration of the instruction, such that any
@@ -114,12 +133,14 @@
                       method,
                       methodProcessingContext);
               if (replacement != null) {
-                // Record if we increased the max number of locals for the method, and reset the
-                // next temporary locals register.
+                // Record if we increased the max number of locals and stack height for the method,
+                // and reset the next temporary locals register.
                 maxLocalsForCode.setMax(maxLocalsForInstruction.getAndSet(cfCode.getMaxLocals()));
+                maxStackForCode.setMax(maxStackForInstruction.getAndSet(cfCode.getMaxStack()));
               } else {
                 // The next temporary locals register should be unchanged.
                 assert maxLocalsForInstruction.get() == cfCode.getMaxLocals();
+                assert maxStackForInstruction.get() == cfCode.getMaxStack();
               }
               return replacement;
             },
@@ -135,6 +156,14 @@
     }
   }
 
+  @Override
+  public CfClassDesugaringCollection createClassDesugaringCollection() {
+    if (recordRewriter == null) {
+      return new EmptyCfClassDesugaringCollection();
+    }
+    return new NonEmptyCfClassDesugaringCollection(recordRewriter);
+  }
+
   private Collection<CfInstruction> desugarInstruction(
       CfInstruction instruction,
       FreshLocalProvider freshLocalProvider,
@@ -220,4 +249,11 @@
       consumer.accept((D8NestBasedAccessDesugaring) nestBasedAccessDesugaring);
     }
   }
+
+  @Override
+  public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
+    if (recordRewriter != null) {
+      consumer.accept(recordRewriter);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
new file mode 100644
index 0000000..19d9ae3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+public interface RecordDesugaringEventConsumer {
+
+  void acceptRecordClass(DexProgramClass recordClass);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
new file mode 100644
index 0000000..67e2c90
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -0,0 +1,272 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfTypeInstruction;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Collections;
+
+public class RecordRewriter implements CfInstructionDesugaring, CfClassDesugaring {
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+
+  public static RecordRewriter create(AppView<?> appView) {
+    return appView.options().shouldDesugarRecords() ? new RecordRewriter(appView) : null;
+  }
+
+  private RecordRewriter(AppView<?> appView) {
+    this.appView = appView;
+    factory = appView.dexItemFactory();
+  }
+
+  public void scan(
+      ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+    CfCode cfCode = programMethod.getDefinition().getCode().asCfCode();
+    for (CfInstruction instruction : cfCode.getInstructions()) {
+      scanInstruction(instruction, eventConsumer);
+    }
+  }
+
+  // The record rewriter scans the cf instructions to figure out if the record class needs to
+  // be added in the output. the analysis cannot be done in desugarInstruction because the analysis
+  // does not rewrite any instruction, and desugarInstruction is expected to rewrite at least one
+  // instruction for assertions to be valid.
+  private void scanInstruction(
+      CfInstruction instruction, CfInstructionDesugaringEventConsumer eventConsumer) {
+    assert !instruction.isInitClass();
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      if (refersToRecord(cfInvoke.getMethod())) {
+        requiresRecordClass(eventConsumer);
+      }
+      return;
+    }
+    if (instruction.isFieldInstruction()) {
+      CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
+      if (refersToRecord(fieldInstruction.getField())) {
+        requiresRecordClass(eventConsumer);
+      }
+      return;
+    }
+    if (instruction.isTypeInstruction()) {
+      CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
+      if (refersToRecord(typeInstruction.getType())) {
+        requiresRecordClass(eventConsumer);
+      }
+      return;
+    }
+    // TODO(b/179146128): Analyse MethodHandle and MethodType.
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+
+    // TODO(b/179146128): This is a temporary work-around to test desugaring of records
+    // without rewriting the record invoke-custom. This should be removed when the record support
+    // is complete.
+    if (instruction.isInvokeDynamic()
+        && context.getHolder().superType == factory.recordType
+        && (context.getReference().match(factory.recordMembers.toString)
+            || context.getReference().match(factory.recordMembers.hashCode)
+            || context.getReference().match(factory.recordMembers.equals))) {
+      requiresRecordClass(eventConsumer);
+      CfInstruction constant =
+          context.getReference().match(factory.recordMembers.toString)
+              ? new CfConstNull()
+              : new CfConstNumber(0, ValueType.INT);
+      return ImmutableList.of(new CfStackInstruction(CfStackInstruction.Opcode.Pop), constant);
+    }
+
+    CfInstruction desugaredInstruction = desugarInstruction(instruction, context);
+    return desugaredInstruction == null ? null : Collections.singletonList(desugaredInstruction);
+  }
+
+  private CfInstruction desugarInstruction(CfInstruction instruction, ProgramMethod context) {
+    assert !instruction.isInitClass();
+    // TODO(b/179146128): Rewrite record invoke-dynamic here.
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      DexMethod newMethod =
+          rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()));
+      if (newMethod != cfInvoke.getMethod()) {
+        return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface());
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    assert !instruction.isInitClass();
+    // TODO(b/179146128): This is a temporary work-around to test desugaring of records
+    // without rewriting the record invoke-custom. This should be removed when the record support
+    // is complete.
+    if (instruction.isInvokeDynamic()
+        && context.getHolder().superType == factory.recordType
+        && (context.getName() == factory.toStringMethodName
+            || context.getName() == factory.hashCodeMethodName
+            || context.getName() == factory.equalsMethodName)) {
+      return true;
+    }
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      return needsDesugaring(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()));
+    }
+    return false;
+  }
+
+  private void requiresRecordClass(RecordDesugaringEventConsumer eventConsumer) {
+    DexProgramClass recordClass = synthesizeR8Record();
+    if (recordClass != null) {
+      eventConsumer.acceptRecordClass(recordClass);
+    }
+  }
+
+  @Override
+  public boolean needsDesugaring(DexProgramClass clazz) {
+    assert clazz.isRecord() || clazz.superType != factory.recordType;
+    return clazz.isRecord();
+  }
+
+  @Override
+  public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
+    if (clazz.isRecord()) {
+      assert clazz.superType == factory.recordType;
+      requiresRecordClass(eventConsumer);
+      clazz.accessFlags.unsetRecord();
+    }
+  }
+
+  private boolean refersToRecord(DexField field) {
+    assert !refersToRecord(field.holder) : "The java.lang.Record class has no fields.";
+    return refersToRecord(field.type);
+  }
+
+  private boolean refersToRecord(DexMethod method) {
+    if (refersToRecord(method.holder)) {
+      return true;
+    }
+    return refersToRecord(method.proto);
+  }
+
+  private boolean refersToRecord(DexProto proto) {
+    if (refersToRecord(proto.returnType)) {
+      return true;
+    }
+    return refersToRecord(proto.parameters.values);
+  }
+
+  private boolean refersToRecord(DexType[] types) {
+    for (DexType type : types) {
+      if (refersToRecord(type)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean refersToRecord(DexType type) {
+    return type == factory.recordType;
+  }
+
+  private boolean needsDesugaring(DexMethod method, boolean isSuper) {
+    return rewriteMethod(method, isSuper) != method;
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  private DexMethod rewriteMethod(DexMethod method, boolean isSuper) {
+    if (method.holder != factory.recordType || method.isInstanceInitializer(factory)) {
+      return method;
+    }
+    assert method == factory.recordMembers.equals
+        || method == factory.recordMembers.hashCode
+        || method == factory.recordMembers.toString;
+    if (isSuper) {
+      // TODO(b/179146128): Support rewriting invoke-super to a Record method.
+      throw new CompilationError("Rewrite invoke-super to abstract method error.");
+    }
+    if (method == factory.recordMembers.equals) {
+      return factory.objectMembers.equals;
+    }
+    if (method == factory.recordMembers.toString) {
+      return factory.objectMembers.toString;
+    }
+    assert method == factory.recordMembers.hashCode;
+    return factory.objectMembers.toString;
+  }
+
+  private DexProgramClass synthesizeR8Record() {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexClass recordClass =
+        appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType);
+    if (recordClass != null && recordClass.isProgramClass()) {
+      return null;
+    }
+    assert recordClass == null || recordClass.isLibraryClass();
+    DexEncodedMethod init = synthesizeRecordInitMethod();
+    // TODO(b/179146128): We may want to remove here the class from the library classes if present
+    //  in cf to cf.
+    return appView
+        .getSyntheticItems()
+        .createFixedClassFromType(
+            SyntheticNaming.SyntheticKind.RECORD_TAG,
+            factory.recordType,
+            factory,
+            builder -> builder.setAbstract().setDirectMethods(Collections.singletonList(init)));
+  }
+
+  private DexEncodedMethod synthesizeRecordInitMethod() {
+    MethodAccessFlags methodAccessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
+    DexEncodedMethod init =
+        new DexEncodedMethod(
+            factory.recordMembers.init,
+            methodAccessFlags,
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            null,
+            true);
+    init.setCode(
+        new CallObjectInitCfCodeProvider(appView, factory.r8RecordType).generateCfCode(), appView);
+    return init;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index da56b69..b7b0db0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -2115,6 +2115,53 @@
         ImmutableList.of());
   }
 
+  public static CfCode IntegerMethods_parseIntSubsequenceWithRadix(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charSequenceType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("subSequence")),
+                true),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.stringType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("parseInt")),
+                false),
+            new CfReturn(ValueType.INT),
+            label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode IntegerMethods_parseUnsignedInt(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -2643,6 +2690,53 @@
         ImmutableList.of());
   }
 
+  public static CfCode LongMethods_parseLongSubsequenceWithRadix(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charSequenceType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("subSequence")),
+                true),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Long;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.longType,
+                        options.itemFactory.stringType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("parseLong")),
+                false),
+            new CfReturn(ValueType.LONG),
+            label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode LongMethods_parseUnsignedLong(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -2670,7 +2764,7 @@
         ImmutableList.of());
   }
 
-  public static CfCode LongMethods_parseUnsignedLongWithRadix(
+  public static CfCode LongMethods_parseUnsignedLongSubsequenceWithRadix(
       InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -2697,20 +2791,15 @@
     return new CfCode(
         method.holder,
         5,
-        10,
+        12,
         ImmutableList.of(
             label0,
-            new CfLoad(ValueType.OBJECT, 0),
-            new CfInvoke(
-                182,
-                options.itemFactory.createMethod(
-                    options.itemFactory.stringType,
-                    options.itemFactory.createProto(options.itemFactory.intType),
-                    options.itemFactory.createString("length")),
-                false),
-            new CfStore(ValueType.INT, 2),
-            label1,
             new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfStore(ValueType.INT, 4),
+            label1,
+            new CfLoad(ValueType.INT, 4),
             new CfIf(If.Type.NE, ValueType.INT, label3),
             label2,
             new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
@@ -2728,25 +2817,29 @@
             label3,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2},
+                    new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
-            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
             new CfConstNumber(2, ValueType.INT),
             new CfIfCmp(If.Type.LT, ValueType.INT, label4),
-            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
             new CfConstNumber(36, ValueType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label5),
             label4,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2},
+                    new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
@@ -2754,7 +2847,7 @@
             new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("illegal radix: ")),
-            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
@@ -2783,15 +2876,17 @@
             label5,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2},
+                    new int[] {0, 1, 2, 3, 4},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(-1, ValueType.LONG),
-            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
             new CfInvoke(
                 184,
@@ -2803,62 +2898,70 @@
                         options.itemFactory.longType),
                     options.itemFactory.createString("divideUnsigned")),
                 false),
-            new CfStore(ValueType.LONG, 3),
+            new CfStore(ValueType.LONG, 5),
             label6,
             new CfLoad(ValueType.OBJECT, 0),
-            new CfConstNumber(0, ValueType.INT),
+            new CfLoad(ValueType.INT, 1),
             new CfInvoke(
-                182,
+                185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.stringType,
+                    options.itemFactory.charSequenceType,
                     options.itemFactory.createProto(
                         options.itemFactory.charType, options.itemFactory.intType),
                     options.itemFactory.createString("charAt")),
-                false),
+                true),
             new CfConstNumber(43, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label7),
-            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 4),
             new CfConstNumber(1, ValueType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label7),
+            new CfLoad(ValueType.INT, 1),
             new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
             new CfGoto(label8),
             label7,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3},
+                    new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
-            new CfConstNumber(0, ValueType.INT),
+            new CfLoad(ValueType.INT, 1),
             label8,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3},
+                    new int[] {0, 1, 2, 3, 4, 5},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
                 new ArrayDeque<>(
                     Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
-            new CfStore(ValueType.INT, 5),
+            new CfStore(ValueType.INT, 7),
             label9,
             new CfConstNumber(0, ValueType.LONG),
-            new CfStore(ValueType.LONG, 6),
+            new CfStore(ValueType.LONG, 8),
             label10,
-            new CfLoad(ValueType.INT, 5),
-            new CfStore(ValueType.INT, 8),
+            new CfLoad(ValueType.INT, 7),
+            new CfStore(ValueType.INT, 10),
             label11,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3, 5, 6, 8},
+                    new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType),
@@ -2867,21 +2970,21 @@
                       FrameType.initialized(options.itemFactory.intType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
-            new CfLoad(ValueType.INT, 8),
+            new CfLoad(ValueType.INT, 10),
             new CfLoad(ValueType.INT, 2),
             new CfIfCmp(If.Type.GE, ValueType.INT, label20),
             label12,
             new CfLoad(ValueType.OBJECT, 0),
-            new CfLoad(ValueType.INT, 8),
+            new CfLoad(ValueType.INT, 10),
             new CfInvoke(
-                182,
+                185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.stringType,
+                    options.itemFactory.charSequenceType,
                     options.itemFactory.createProto(
                         options.itemFactory.charType, options.itemFactory.intType),
                     options.itemFactory.createString("charAt")),
-                false),
-            new CfLoad(ValueType.INT, 1),
+                true),
+            new CfLoad(ValueType.INT, 3),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
@@ -2892,9 +2995,9 @@
                         options.itemFactory.intType),
                     options.itemFactory.createString("digit")),
                 false),
-            new CfStore(ValueType.INT, 9),
+            new CfStore(ValueType.INT, 11),
             label13,
-            new CfLoad(ValueType.INT, 9),
+            new CfLoad(ValueType.INT, 11),
             new CfConstNumber(-1, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label15),
             label14,
@@ -2902,6 +3005,13 @@
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
                     options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
@@ -2913,9 +3023,11 @@
             label15,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3, 5, 6, 8, 9},
+                    new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType),
@@ -2925,21 +3037,21 @@
                       FrameType.initialized(options.itemFactory.intType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
-            new CfLoad(ValueType.LONG, 6),
+            new CfLoad(ValueType.LONG, 8),
             new CfConstNumber(0, ValueType.LONG),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.LT, ValueType.INT, label17),
-            new CfLoad(ValueType.LONG, 6),
-            new CfLoad(ValueType.LONG, 3),
+            new CfLoad(ValueType.LONG, 8),
+            new CfLoad(ValueType.LONG, 5),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.GT, ValueType.INT, label17),
-            new CfLoad(ValueType.LONG, 6),
-            new CfLoad(ValueType.LONG, 3),
+            new CfLoad(ValueType.LONG, 8),
+            new CfLoad(ValueType.LONG, 5),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.NE, ValueType.INT, label18),
-            new CfLoad(ValueType.INT, 9),
+            new CfLoad(ValueType.INT, 11),
             new CfConstNumber(-1, ValueType.LONG),
-            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 3),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
             label16,
             new CfInvoke(
@@ -2957,9 +3069,11 @@
             label17,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3, 5, 6, 8, 9},
+                    new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType),
@@ -2974,6 +3088,13 @@
             new CfConstString(options.itemFactory.createString("Too large for unsigned long: ")),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
                     options.itemFactory.stringType,
@@ -2993,9 +3114,11 @@
             label18,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3, 5, 6, 8, 9},
+                    new int[] {0, 1, 2, 3, 4, 5, 7, 8, 10, 11},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType),
@@ -3005,23 +3128,25 @@
                       FrameType.initialized(options.itemFactory.intType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
-            new CfLoad(ValueType.LONG, 6),
-            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.LONG, 8),
+            new CfLoad(ValueType.INT, 3),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.LONG),
-            new CfLoad(ValueType.INT, 9),
+            new CfLoad(ValueType.INT, 11),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.LONG),
-            new CfStore(ValueType.LONG, 6),
+            new CfStore(ValueType.LONG, 8),
             label19,
-            new CfIinc(8, 1),
+            new CfIinc(10, 1),
             new CfGoto(label11),
             label20,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3, 5, 6},
+                    new int[] {0, 1, 2, 3, 4, 5, 7, 8},
                     new FrameType[] {
-                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType),
@@ -3029,13 +3154,52 @@
                       FrameType.initialized(options.itemFactory.longType)
                     }),
                 new ArrayDeque<>(Arrays.asList())),
-            new CfLoad(ValueType.LONG, 6),
+            new CfLoad(ValueType.LONG, 8),
             new CfReturn(ValueType.LONG),
             label21),
         ImmutableList.of(),
         ImmutableList.of());
   }
 
+  public static CfCode LongMethods_parseUnsignedLongWithRadix(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfConstNumber(0, ValueType.INT),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringType,
+                    options.itemFactory.createProto(options.itemFactory.intType),
+                    options.itemFactory.createString("length")),
+                false),
+            new CfLoad(ValueType.INT, 1),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Long;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.longType,
+                        options.itemFactory.charSequenceType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("parseUnsignedLong")),
+                false),
+            new CfReturn(ValueType.LONG),
+            label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode LongMethods_remainderUnsigned(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index 51949c9..8b928aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import java.util.Collection;
 import java.util.Collections;
 import org.objectweb.asm.Opcodes;
@@ -34,7 +35,8 @@
   public static MethodInvokeRewriter rewriteAsIdentity() {
     return new FullMethodInvokeRewriter() {
       @Override
-      public Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) {
+      public Collection<CfInstruction> rewrite(
+          CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) {
         // The invoke consumes the stack value and pushes another assumed to be the same.
         return Collections.emptyList();
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index f8ab4a7..20998db 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.objectweb.asm.Opcodes;
@@ -32,8 +33,10 @@
     return new FullMethodInvokeRewriter() {
 
       @Override
-      public Collection<CfInstruction> rewrite(CfInvoke invoke, DexItemFactory factory) {
+      public Collection<CfInstruction> rewrite(
+          CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator) {
         // requireNonNull returns the operand, so dup top-of-stack, do getClass and pop the class.
+        localStackAllocator.allocateLocalStack(1);
         return ImmutableList.of(
             new CfStackInstruction(Opcode.Dup),
             new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index 2518219..44a37b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -45,8 +45,8 @@
           DexClass hostClass = nest.getHostClass();
           for (DexClass memberClass : nest.getMembers()) {
             if (hostClass.isProgramClass() || memberClass.isProgramClass()) {
-              reportDependencyEdge(hostClass, memberClass, appView.options());
-              reportDependencyEdge(memberClass, hostClass, appView.options());
+              reportDependencyEdge(hostClass, memberClass, appView.appInfo());
+              reportDependencyEdge(memberClass, hostClass, appView.appInfo());
             }
           }
         },
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index d01470e..d3dc297 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -27,11 +27,14 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
 import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 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.ConstantOrNonConstantNumberValue;
 import com.android.tools.r8.ir.analysis.value.SingleConstClassValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
@@ -54,6 +57,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.InstanceFieldInstruction;
+import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
@@ -1092,6 +1096,10 @@
       BasicBlock block,
       InstructionListIterator iterator,
       IntSwitch theSwitch) {
+    if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) {
+      return;
+    }
+
     if (theSwitch.numberOfKeys() == 1) {
       rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch);
       return;
@@ -1186,6 +1194,27 @@
     }
   }
 
+  // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId
+  //  field values (from horizontal class merging. See bug for more details.
+  private boolean disableSwitchToIfRewritingForClassIdComparisons(IntSwitch theSwitch) {
+    Value switchValue = theSwitch.value().getAliasedValue();
+    if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) {
+      return false;
+    }
+    AppInfoWithLiveness appInfo = appView.appInfoWithLiveness();
+    if (appInfo == null) {
+      return false;
+    }
+    InstanceGet instanceGet = switchValue.getDefinition().asInstanceGet();
+    SuccessfulFieldResolutionResult resolutionResult =
+        appInfo.resolveField(instanceGet.getField()).asSuccessfulResolution();
+    if (resolutionResult == null) {
+      return false;
+    }
+    DexEncodedField resolvedField = resolutionResult.getResolvedField();
+    return HorizontalClassMergerUtils.isClassIdField(appView, resolvedField);
+  }
+
   private SwitchCaseEliminator removeUnnecessarySwitchCases(
       IRCode code,
       Switch theSwitch,
@@ -1197,6 +1226,7 @@
         new BasicBlockBehavioralSubsumption(appView, code);
 
     // Compute the set of switch cases that can be removed.
+    AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context());
     for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
       BasicBlock targetBlock = theSwitch.targetBlock(i);
 
@@ -1210,7 +1240,7 @@
 
       // This switch case can be removed if the behavior of the target block is equivalent to the
       // behavior of the default block, or if the switch case is unreachable.
-      if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, i)
+      if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)
           || behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) {
         if (eliminator == null) {
           eliminator = new SwitchCaseEliminator(theSwitch, iterator);
@@ -1218,6 +1248,16 @@
         eliminator.markSwitchCaseForRemoval(i);
       }
     }
+
+    if (eliminator == null || eliminator.isFallthroughLive()) {
+      if (switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) {
+        if (eliminator == null) {
+          eliminator = new SwitchCaseEliminator(theSwitch, iterator);
+        }
+        eliminator.markSwitchFallthroughAsNeverHit();
+      }
+    }
+
     if (eliminator != null) {
       eliminator.optimize();
     }
@@ -2619,6 +2659,16 @@
       }
     }
 
+    if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
+      AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
+      if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
+          && !lhsAbstractValue.asConstantOrNonConstantNumberValue().containsInt(0)) {
+        // Value doesn't contain zero at all.
+        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
+        return true;
+      }
+    }
+
     if (lhs.hasValueRange()) {
       LongInterval interval = lhs.getValueRange();
       if (!interval.containsValue(0)) {
@@ -2691,6 +2741,23 @@
       return true;
     }
 
+    if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
+      AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
+      AbstractValue rhsAbstractValue = rhs.getAbstractValue(appView, code.context());
+      if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
+          && rhsAbstractValue.isConstantOrNonConstantNumberValue()) {
+        ConstantOrNonConstantNumberValue lhsNumberValue =
+            lhsAbstractValue.asConstantOrNonConstantNumberValue();
+        ConstantOrNonConstantNumberValue rhsNumberValue =
+            rhsAbstractValue.asConstantOrNonConstantNumberValue();
+        if (!lhsNumberValue.mayOverlapWith(rhsNumberValue)) {
+          // No overlap.
+          simplifyIfWithKnownCondition(code, block, theIf, 1);
+          return true;
+        }
+      }
+    }
+
     if (lhs.hasValueRange() && rhs.hasValueRange()) {
       // Zero test with a value range, or comparison between between two values,
       // each with a value ranges.
@@ -3372,6 +3439,7 @@
       Instruction arrayDefinition = array.getDefinition();
       assert arrayDefinition != null;
 
+      Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers();
       if (arrayDefinition.isNewArrayEmpty()) {
         Value size = arrayDefinition.asNewArrayEmpty().size();
         arrayLength.outValue().replaceUsers(size);
@@ -3380,7 +3448,11 @@
         int size = (int) arrayDefinition.asNewArrayFilledData().size;
         ConstNumber constSize = code.createIntConstant(size);
         iterator.replaceCurrentInstruction(constSize);
+      } else {
+        continue;
       }
+
+      phiUsers.forEach(Phi::removeTrivialPhi);
       // TODO(139489070): static-get of constant array
     }
     assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 33e414f..8e66601 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -201,13 +201,13 @@
     // If we do this it can increase the size of the main dex dependent classes.
     if (reason != Reason.FORCE
         && inliner.mainDexInfo.disallowInliningIntoContext(
-            appView.appInfo(), method, singleTarget)) {
+            appView.appInfo(), method, singleTarget, appView.getSyntheticItems())) {
       whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
       return false;
     }
     assert reason != Reason.FORCE
         || !inliner.mainDexInfo.disallowInliningIntoContext(
-            appView.appInfo(), method, singleTarget);
+            appView.appInfo(), method, singleTarget, appView.getSyntheticItems());
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index f5f4d70..fa3886e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -391,6 +391,9 @@
   }
 
   private boolean isRebindingNewClassIntoMainDex(ProgramMethod context, DexMethod reboundMethod) {
-    return !appView.appInfo().getMainDexInfo().canRebindReference(context, reboundMethod);
+    return !appView
+        .appInfo()
+        .getMainDexInfo()
+        .canRebindReference(context, reboundMethod, appView.getSyntheticItems());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 289b2ba..802fdb3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -92,7 +92,8 @@
             && !appView
                 .appInfo()
                 .getMainDexInfo()
-                .canRebindReference(code.context(), baseClass.getType())) {
+                .canRebindReference(
+                    code.context(), baseClass.getType(), appView.getSyntheticItems())) {
           return;
         }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
index 6c9bfbb..11e0b51 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.StringSwitch;
 import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.utils.BooleanUtils;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@@ -28,6 +29,7 @@
 
   private int alwaysHitCase = -1;
   private BasicBlock alwaysHitTarget;
+  private boolean liveFallthrough = true;
   private boolean mayHaveIntroducedUnreachableBlocks = false;
   private IntSet switchCasesToBeRemoved;
 
@@ -39,13 +41,13 @@
   }
 
   private boolean allSwitchCasesMarkedForRemoval() {
-    assert switchCasesToBeRemoved != null;
-    return switchCasesToBeRemoved.size() == theSwitch.numberOfKeys();
+    return switchCasesToBeRemoved != null
+        && switchCasesToBeRemoved.size() == theSwitch.numberOfKeys();
   }
 
   private boolean canBeOptimized() {
     assert switchCasesToBeRemoved == null || !switchCasesToBeRemoved.isEmpty();
-    return switchCasesToBeRemoved != null || hasAlwaysHitCase();
+    return switchCasesToBeRemoved != null || hasAlwaysHitCase() || !isFallthroughLive();
   }
 
   boolean mayHaveIntroducedUnreachableBlocks() {
@@ -56,11 +58,19 @@
     if (hasAlwaysHitCase()) {
       return index == alwaysHitCase;
     }
-    return !switchCasesToBeRemoved.contains(index);
+    if (switchCasesToBeRemoved != null) {
+      return !switchCasesToBeRemoved.contains(index);
+    }
+    assert !isFallthroughLive();
+    return true;
+  }
+
+  public boolean isFallthroughDead() {
+    return !isFallthroughLive();
   }
 
   public boolean isFallthroughLive() {
-    return !hasAlwaysHitCase();
+    return liveFallthrough;
   }
 
   public boolean hasAlwaysHitCase() {
@@ -71,6 +81,7 @@
     assert alwaysHitCase < 0;
     alwaysHitCase = i;
     alwaysHitTarget = theSwitch.targetBlock(i);
+    liveFallthrough = false;
   }
 
   void markSwitchCaseForRemoval(int i) {
@@ -80,6 +91,11 @@
     switchCasesToBeRemoved.add(i);
   }
 
+  void markSwitchFallthroughAsNeverHit() {
+    assert !hasAlwaysHitCase();
+    liveFallthrough = false;
+  }
+
   boolean optimize() {
     if (canBeOptimized()) {
       int originalNumberOfSuccessors = block.getSuccessors().size();
@@ -147,53 +163,70 @@
       targetBlockIndexOffset[i] += targetBlockIndexOffset[i - 1];
     }
 
-    int newNumberOfKeys = theSwitch.numberOfKeys() - switchCasesToBeRemoved.size();
+    int newFallthrough = theSwitch.numberOfKeys();
+    if (isFallthroughDead()) {
+      for (int i = theSwitch.numberOfKeys() - 1; i >= 0; i--) {
+        if (isSwitchCaseLive(i)) {
+          newFallthrough = i;
+          break;
+        }
+      }
+    }
+
+    // Compute the number of removed switch cases. If the fallthrough is dead, we promote one of the
+    // live switch cases to being the fallthrough instead.
+    int numberOfRemovedSwitchCases =
+        switchCasesToBeRemoved != null ? switchCasesToBeRemoved.size() : 0;
+    numberOfRemovedSwitchCases += BooleanUtils.intValue(isFallthroughDead());
+
+    int newNumberOfKeys = theSwitch.numberOfKeys() - numberOfRemovedSwitchCases;
     int[] newTargetBlockIndices = new int[newNumberOfKeys];
-    for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
-      if (!switchCasesToBeRemoved.contains(i)) {
+    for (int i = 0, j = 0; i < newFallthrough; i++) {
+      if (isSwitchCaseLive(i)) {
         newTargetBlockIndices[j] =
             theSwitch.getTargetBlockIndex(i)
                 - targetBlockIndexOffset[theSwitch.getTargetBlockIndex(i)];
         assert newTargetBlockIndices[j] < block.getSuccessors().size();
-        assert newTargetBlockIndices[j] != theSwitch.getFallthroughBlockIndex();
         j++;
       }
     }
 
+    int fallthroughBlockIndex;
+    if (isFallthroughLive()) {
+      fallthroughBlockIndex =
+          theSwitch.getFallthroughBlockIndex()
+              - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()];
+    } else {
+      fallthroughBlockIndex =
+          theSwitch.getTargetBlockIndex(newFallthrough)
+              - targetBlockIndexOffset[theSwitch.getTargetBlockIndex(newFallthrough)];
+    }
+
     Switch replacement;
     if (theSwitch.isIntSwitch()) {
       IntSwitch intSwitch = theSwitch.asIntSwitch();
       int[] newKeys = new int[newNumberOfKeys];
-      for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
-        if (!switchCasesToBeRemoved.contains(i)) {
+      for (int i = 0, j = 0; i < newFallthrough; i++) {
+        if (isSwitchCaseLive(i)) {
           newKeys[j] = intSwitch.getKey(i);
           j++;
         }
       }
       replacement =
-          new IntSwitch(
-              theSwitch.value(),
-              newKeys,
-              newTargetBlockIndices,
-              theSwitch.getFallthroughBlockIndex()
-                  - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+          new IntSwitch(theSwitch.value(), newKeys, newTargetBlockIndices, fallthroughBlockIndex);
     } else {
       assert theSwitch.isStringSwitch();
       StringSwitch stringSwitch = theSwitch.asStringSwitch();
       DexString[] newKeys = new DexString[newNumberOfKeys];
-      for (int i = 0, j = 0; i < theSwitch.numberOfKeys(); i++) {
-        if (!switchCasesToBeRemoved.contains(i)) {
+      for (int i = 0, j = 0; i < newFallthrough; i++) {
+        if (isSwitchCaseLive(i)) {
           newKeys[j] = stringSwitch.getKey(i);
           j++;
         }
       }
       replacement =
           new StringSwitch(
-              theSwitch.value(),
-              newKeys,
-              newTargetBlockIndices,
-              theSwitch.getFallthroughBlockIndex()
-                  - targetBlockIndexOffset[theSwitch.getFallthroughBlockIndex()]);
+              theSwitch.value(), newKeys, newTargetBlockIndices, fallthroughBlockIndex);
     }
     iterator.replaceCurrentInstruction(replacement);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 78dc973..4857999 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -183,14 +183,11 @@
           continue;
         }
 
-        assert processor.getReceivers().verifyReceiverSetsAreDisjoint();
-
         // Is inlining allowed.
         InliningIRProvider inliningIRProvider =
             new InliningIRProvider(appView, method, code, methodProcessor);
         ClassInlinerCostAnalysis costAnalysis =
-            new ClassInlinerCostAnalysis(
-                appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases());
+            new ClassInlinerCostAnalysis(appView, inliningIRProvider, processor.getReceivers());
         if (costAnalysis.willExceedInstructionBudget(
             code,
             processor.getEligibleClass(),
@@ -213,8 +210,7 @@
         // Inline the class instance.
         Set<Value> affectedValues = Sets.newIdentityHashSet();
         try {
-          anyInlinedMethods |=
-              processor.processInlining(code, affectedValues, defaultOracle, inliningIRProvider);
+          anyInlinedMethods |= processor.processInlining(code, affectedValues, inliningIRProvider);
         } catch (IllegalClassInlinerStateException e) {
           // We introduced a user that we cannot handle in the class inliner as a result of force
           // inlining. Abort gracefully from class inlining without removing the instance.
@@ -227,8 +223,6 @@
           anyInlinedMethods = true;
         }
 
-        assert inliningIRProvider.verifyIRCacheIsEmpty();
-
         // Restore normality.
         code.removeAllDeadAndTrivialPhis(affectedValues);
         if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 3feac87..33fa6fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -28,17 +28,17 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final InliningIRProvider inliningIRProvider;
-  private final Set<Value> definiteReceiverAliases;
+  private final ClassInlinerReceiverSet receivers;
 
   private int estimatedCost = 0;
 
   ClassInlinerCostAnalysis(
       AppView<AppInfoWithLiveness> appView,
       InliningIRProvider inliningIRProvider,
-      Set<Value> definiteReceiverAliases) {
+      ClassInlinerReceiverSet receivers) {
     this.appView = appView;
     this.inliningIRProvider = inliningIRProvider;
-    this.definiteReceiverAliases = definiteReceiverAliases;
+    this.receivers = receivers;
   }
 
   boolean willExceedInstructionBudget(
@@ -145,10 +145,10 @@
     Set<Value> receiverAliasesInInlinee = Sets.newIdentityHashSet();
     for (int i = 0; i < invoke.inValues().size(); i++) {
       Value inValue = invoke.inValues().get(i);
-      if (definiteReceiverAliases.contains(inValue)) {
+      if (receivers.isReceiverAlias(inValue)) {
         receiverAliasesInInlinee.add(arguments.get(i));
       } else {
-        assert !definiteReceiverAliases.contains(inValue.getAliasedValue());
+        assert !receivers.isReceiverAlias(inValue.getAliasedValue());
       }
     }
     return receiverAliasesInInlinee;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
deleted file mode 100644
index 78e756d..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.classinliner;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.Pair;
-import java.util.List;
-
-public class ClassInlinerEligibilityInfo {
-
-  final List<Pair<Invoke.Type, DexMethod>> callsReceiver;
-
-  /**
-   * Set to {@link OptionalBool#TRUE} if the method is guaranteed to return the receiver, {@link
-   * OptionalBool#FALSE} if the method is guaranteed not to return the receiver, and {@link
-   * OptionalBool#UNKNOWN} if the method may return the receiver.
-   */
-  final OptionalBool returnsReceiver;
-
-  public final boolean hasMonitorOnReceiver;
-  public final boolean modifiesInstanceFields;
-
-  public ClassInlinerEligibilityInfo(
-      List<Pair<Invoke.Type, DexMethod>> callsReceiver,
-      OptionalBool returnsReceiver,
-      boolean hasMonitorOnReceiver,
-      boolean modifiesInstanceFields) {
-    this.callsReceiver = callsReceiver;
-    this.returnsReceiver = returnsReceiver;
-    this.hasMonitorOnReceiver = hasMonitorOnReceiver;
-    this.modifiesInstanceFields = modifiesInstanceFields;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
deleted file mode 100644
index e0f991d..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.classinliner;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.BooleanLatticeElement;
-import com.android.tools.r8.utils.OptionalBool;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This analysis determines whether a method returns the receiver. The analysis is specific to the
- * class inliner, and the result is therefore not sound in general.
- *
- * <p>The analysis makes the following assumptions.
- *
- * <ul>
- *   <li>None of the given method's arguments is an alias of the receiver (except for the receiver
- *       itself).
- *   <li>The receiver is not stored in the heap. Thus, it is guaranteed that (i) all field-get
- *       instructions do not return an alias of the receiver, and (ii) invoke instructions can only
- *       return an alias of the receiver if the receiver is given as an argument.
- * </ul>
- */
-public class ClassInlinerReceiverAnalysis {
-
-  private final AppView<?> appView;
-  private final DexEncodedMethod method;
-  private final IRCode code;
-  private final Value receiver;
-
-  private final Map<Value, OptionalBool> isReceiverAliasCache = new IdentityHashMap<>();
-
-  public ClassInlinerReceiverAnalysis(AppView<?> appView, DexEncodedMethod method, IRCode code) {
-    this.appView = appView;
-    this.method = method;
-    this.code = code;
-    this.receiver = code.getThis();
-    assert !receiver.hasAliasedValue();
-    assert receiver.getType().isClassType();
-  }
-
-  public OptionalBool computeReturnsReceiver() {
-    if (method.method.proto.returnType.isVoidType()) {
-      return OptionalBool.FALSE;
-    }
-
-    List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
-    if (normalExitBlocks.isEmpty()) {
-      return OptionalBool.FALSE;
-    }
-
-    BooleanLatticeElement result = OptionalBool.BOTTOM;
-    for (BasicBlock block : normalExitBlocks) {
-      Value returnValue = block.exit().asReturn().returnValue();
-      result = result.join(getOrComputeIsReceiverAlias(returnValue));
-
-      // Stop as soon as we reach unknown.
-      if (result.isUnknown()) {
-        return OptionalBool.UNKNOWN;
-      }
-    }
-    assert !result.isBottom();
-    return result.asOptionalBool();
-  }
-
-  private OptionalBool getOrComputeIsReceiverAlias(Value value) {
-    Value root = value.getAliasedValue();
-    return isReceiverAliasCache.computeIfAbsent(root, this::computeIsReceiverAlias);
-  }
-
-  private OptionalBool computeIsReceiverAlias(Value value) {
-    assert !value.hasAliasedValue();
-
-    if (value == receiver) {
-      // Guaranteed to return the receiver.
-      return OptionalBool.TRUE;
-    }
-
-    ClassTypeElement valueType = value.getType().asClassType();
-    if (valueType == null) {
-      return OptionalBool.FALSE;
-    }
-
-    ClassTypeElement receiverType = receiver.getType().asClassType();
-    if (!valueType.isRelatedTo(receiverType, appView)) {
-      // Guaranteed not to return the receiver.
-      return OptionalBool.FALSE;
-    }
-
-    if (value.isPhi()) {
-      // Not sure what is returned.
-      return OptionalBool.UNKNOWN;
-    }
-
-    Instruction definition = value.definition;
-    if (definition.isArrayGet() || definition.isFieldGet()) {
-      // Guaranteed not to return the receiver, since the class inliner does not allow the
-      // receiver to flow into any arrays or fields.
-      return OptionalBool.FALSE;
-    }
-
-    if (definition.isConstInstruction() || definition.isCreatingInstanceOrArray()) {
-      // Guaranteed not to return the receiver.
-      return OptionalBool.FALSE;
-    }
-
-    if (definition.isInvokeMethod()) {
-      // Since the class inliner does not allow the receiver to flow into the heap, the only way for
-      // the invoked method to return the receiver is if one of the given arguments is an alias of
-      // the receiver.
-      InvokeMethod invoke = definition.asInvokeMethod();
-      for (Value argument : invoke.arguments()) {
-        if (getOrComputeIsReceiverAlias(argument).isPossiblyTrue()) {
-          return OptionalBool.UNKNOWN;
-        }
-      }
-
-      return OptionalBool.FALSE;
-    }
-
-    // Not sure what is returned.
-    return OptionalBool.UNKNOWN;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
index 5a8c59d..1d73fdc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverSet.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.optimize.classinliner;
 
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.AliasKind;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -19,8 +18,7 @@
 
   private final Value root;
 
-  private final Set<Value> definiteReceiverAliases;
-  private final Set<Value> maybeReceiverAliases = Sets.newIdentityHashSet();
+  private final Set<Value> receiverAliases;
 
   // Set of values that are not allowed to become an alias of the receiver.
   private final Set<Value> illegalReceiverAliases = Sets.newIdentityHashSet();
@@ -30,32 +28,18 @@
       new IdentityHashMap<>();
 
   ClassInlinerReceiverSet(Value root) {
-    this.definiteReceiverAliases = SetUtils.newIdentityHashSet(root);
+    this.receiverAliases = SetUtils.newIdentityHashSet(root);
     this.root = root;
   }
 
-  Set<Value> getDefiniteReceiverAliases() {
-    return definiteReceiverAliases;
-  }
-
-  Set<Value> getMaybeReceiverAliases() {
-    return maybeReceiverAliases;
-  }
-
-  boolean addReceiverAlias(Value alias, AliasKind kind) {
+  boolean addReceiverAlias(Value alias) {
     if (isIllegalReceiverAlias(alias)) {
       return false; // Not allowed.
     }
     // All checks passed.
     deferredAliasValidityChecks.remove(alias);
     boolean changed;
-    if (kind == AliasKind.DEFINITE) {
-      assert !maybeReceiverAliases.contains(alias);
-      changed = definiteReceiverAliases.add(alias);
-    } else {
-      assert !definiteReceiverAliases.contains(alias);
-      changed = maybeReceiverAliases.add(alias);
-    }
+    changed = receiverAliases.add(alias);
     // Verify that the state changed. Otherwise, we are analyzing the same instruction more than
     // once.
     assert changed : alias.toString() + " already added as an alias";
@@ -87,15 +71,11 @@
   }
 
   boolean isReceiverAlias(Value value) {
-    return isDefiniteReceiverAlias(value) || isMaybeReceiverAlias(value);
+    return isDefiniteReceiverAlias(value);
   }
 
   boolean isDefiniteReceiverAlias(Value value) {
-    return definiteReceiverAliases.contains(value);
-  }
-
-  private boolean isMaybeReceiverAlias(Value value) {
-    return maybeReceiverAliases.contains(value);
+    return receiverAliases.contains(value);
   }
 
   private boolean isIllegalReceiverAlias(Value value) {
@@ -115,13 +95,7 @@
 
   void reset() {
     deferredAliasValidityChecks.clear();
-    definiteReceiverAliases.clear();
-    definiteReceiverAliases.add(root);
-    maybeReceiverAliases.clear();
-  }
-
-  boolean verifyReceiverSetsAreDisjoint() {
-    assert Sets.intersection(getMaybeReceiverAliases(), getDefiniteReceiverAliases()).isEmpty();
-    return true;
+    receiverAliases.clear();
+    receiverAliases.add(root);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 32e25f1..854f6e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -4,13 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
-import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,23 +23,23 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InstructionOrPhi;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
@@ -48,29 +48,25 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.EligibilityStatus;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -80,18 +76,10 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 final class InlineCandidateProcessor {
 
-  enum AliasKind {
-    DEFINITE,
-    MAYBE
-  }
-
-  private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
-      ImmutableSet.of(If.Type.EQ, If.Type.NE);
   private static final AliasedValueConfiguration aliasesThroughAssumeAndCheckCasts =
       AssumeAndCheckCastAliasedValueConfiguration.getInstance();
 
@@ -105,15 +93,11 @@
 
   private Value eligibleInstance;
   private DexProgramClass eligibleClass;
+  private ObjectState objectState;
 
-  private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
-      new IdentityHashMap<>();
+  private final Map<InvokeMethod, InliningInfo> directMethodCalls = new IdentityHashMap<>();
 
   private final ProgramMethodSet indirectMethodCallsOnInstance = ProgramMethodSet.create();
-  private final Map<InvokeMethod, InliningInfo> extraMethodCalls
-      = new IdentityHashMap<>();
-  private final List<Pair<InvokeMethod, Integer>> unusedArguments
-      = new ArrayList<>();
 
   private final Map<InvokeMethod, ProgramMethod> directInlinees = new IdentityHashMap<>();
   private final List<ProgramMethod> indirectInlinees = new ArrayList<>();
@@ -203,6 +187,11 @@
     if (eligibleClass == null) {
       return EligibilityStatus.NOT_ELIGIBLE;
     }
+    AbstractValue abstractValue = optimizationInfo.getAbstractValue();
+    objectState =
+        abstractValue.isSingleFieldValue()
+            ? abstractValue.asSingleFieldValue().getState()
+            : ObjectState.empty();
     return EligibilityStatus.ELIGIBLE;
   }
 
@@ -255,7 +244,7 @@
           if (alias.hasPhiUsers()) {
             return alias.firstPhiUser(); // Not eligible.
           }
-          if (!receivers.addReceiverAlias(alias, AliasKind.DEFINITE)) {
+          if (!receivers.addReceiverAlias(alias)) {
             return user; // Not eligible.
           }
           indirectUsers.addAll(alias.uniqueUsers());
@@ -263,10 +252,6 @@
         }
 
         if (user.isInstanceGet()) {
-          if (root.isStaticGet()) {
-            // We don't have a replacement for this field read.
-            return user; // Not eligible.
-          }
           DexEncodedField field =
               appView
                   .appInfo()
@@ -275,6 +260,11 @@
           if (field == null || field.isStatic()) {
             return user; // Not eligible.
           }
+          if (root.isStaticGet()
+              && !objectState.hasMaterializableFieldValueThatMatches(
+                  appView, field, method, AbstractValue::isSingleConstValue)) {
+            return user; // Not eligible.
+          }
           continue;
         }
 
@@ -298,11 +288,11 @@
         }
 
         if (user.isInvokeMethod()) {
-          InvokeMethod invokeMethod = user.asInvokeMethod();
+          InvokeMethod invoke = user.asInvokeMethod();
           SingleResolutionResult resolutionResult =
               appView
                   .appInfo()
-                  .resolveMethod(invokeMethod.getInvokedMethod(), invokeMethod.getInterfaceBit())
+                  .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
                   .asSingleResolution();
           if (resolutionResult == null
               || resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
@@ -310,13 +300,13 @@
           }
 
           // TODO(b/156853206): Avoid duplicating resolution.
-          DexClassAndMethod singleTarget = invokeMethod.lookupSingleTarget(appView, method);
+          DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, method);
           if (singleTarget == null) {
             return user; // Not eligible.
           }
 
           if (singleTarget.isLibraryMethod()
-              && isEligibleLibraryMethodCall(invokeMethod, singleTarget.asLibraryMethod())) {
+              && isEligibleLibraryMethodCall(invoke, singleTarget.asLibraryMethod())) {
             continue;
           }
 
@@ -331,44 +321,25 @@
           }
 
           // Eligible constructor call (for new instance roots only).
-          if (user.isInvokeDirect()) {
-            InvokeDirect invoke = user.asInvokeDirect();
-            if (dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
-              boolean isCorrespondingConstructorCall =
-                  root.isNewInstance()
-                      && !invoke.inValues().isEmpty()
-                      && root.outValue() == invoke.getReceiver();
-              if (isCorrespondingConstructorCall) {
-                InliningInfo inliningInfo = isEligibleConstructorCall(invoke, singleProgramTarget);
-                if (inliningInfo != null) {
-                  methodCallsOnInstance.put(invoke, inliningInfo);
-                  continue;
-                }
+          if (user.isInvokeConstructor(dexItemFactory)) {
+            InvokeDirect invokeDirect = user.asInvokeDirect();
+            boolean isCorrespondingConstructorCall =
+                root.isNewInstance() && root.outValue() == invokeDirect.getReceiver();
+            if (isCorrespondingConstructorCall) {
+              InliningInfo inliningInfo =
+                  isEligibleConstructorCall(invokeDirect, singleProgramTarget);
+              if (inliningInfo != null) {
+                directMethodCalls.put(invoke, inliningInfo);
+                continue;
               }
-              assert !isExtraMethodCall(invoke);
-              return user; // Not eligible.
             }
+            return user; // Not eligible.
           }
 
-          // Eligible virtual method call on the instance as a receiver.
-          if (user.isInvokeVirtual() || user.isInvokeInterface()) {
-            InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
-            InliningInfo inliningInfo =
-                isEligibleDirectVirtualMethodCall(
-                    invoke, resolutionResult, singleProgramTarget, indirectUsers, defaultOracle);
-            if (inliningInfo != null) {
-              methodCallsOnInstance.put(invoke, inliningInfo);
-              continue;
-            }
-          }
-
-          // Eligible usage as an invocation argument.
-          if (isExtraMethodCall(invokeMethod)) {
-            assert !invokeMethod.isInvokeSuper();
-            assert !invokeMethod.isInvokePolymorphic();
-            if (isExtraMethodCallEligible(invokeMethod, singleProgramTarget, defaultOracle)) {
-              continue;
-            }
+          // Eligible non-constructor method call.
+          if (isEligibleDirectMethodCall(
+              invoke, resolutionResult, singleProgramTarget, defaultOracle, indirectUsers)) {
+            continue;
           }
 
           return user; // Not eligible.
@@ -395,7 +366,6 @@
   // Process inlining, includes the following steps:
   //
   //  * remove linked assume instructions if any so that users of the eligible field are up-to-date.
-  //  * replace unused instance usages as arguments which are never used
   //  * inline extra methods if any, collect new direct method calls
   //  * inline direct methods if any
   //  * remove superclass initializer call and field reads
@@ -406,30 +376,12 @@
   boolean processInlining(
       IRCode code,
       Set<Value> affectedValues,
-      Supplier<InliningOracle> defaultOracle,
       InliningIRProvider inliningIRProvider)
       throws IllegalClassInlinerStateException {
     // Verify that `eligibleInstance` is not aliased.
     assert eligibleInstance == eligibleInstance.getAliasedValue();
-    replaceUsagesAsUnusedArgument(code);
 
-    boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
-    if (anyInlinedMethods) {
-      // Reset the collections.
-      clear();
-
-      // Repeat user analysis
-      InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
-      if (ineligibleUser != null) {
-        throw new IllegalClassInlinerStateException();
-      }
-      assert extraMethodCalls.isEmpty()
-          : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
-      assert unusedArguments.isEmpty()
-          : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
-    }
-
-    anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
+    boolean anyInlinedMethods = forceInlineDirectMethodInvocations(code, inliningIRProvider);
     anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
 
     rebindIndirectEligibleInstanceUsersFromPhis();
@@ -440,60 +392,21 @@
     return anyInlinedMethods;
   }
 
-  private void clear() {
-    methodCallsOnInstance.clear();
-    indirectMethodCallsOnInstance.clear();
-    extraMethodCalls.clear();
-    unusedArguments.clear();
-    receivers.reset();
-  }
-
-  private void replaceUsagesAsUnusedArgument(IRCode code) {
-    for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) {
-      InvokeMethod invoke = unusedArgument.getFirst();
-      BasicBlock block = invoke.getBlock();
-
-      ConstNumber nullValue = code.createConstNull();
-      nullValue.setPosition(invoke.getPosition());
-      block.listIterator(code, invoke).add(nullValue);
-      assert nullValue.getBlock() == block;
-
-      int argIndex = unusedArgument.getSecond();
-      invoke.replaceValue(argIndex, nullValue.outValue());
-    }
-    unusedArguments.clear();
-  }
-
-  private boolean forceInlineExtraMethodInvocations(
-      IRCode code, InliningIRProvider inliningIRProvider) {
-    if (extraMethodCalls.isEmpty()) {
-      return false;
-    }
-    // Inline extra methods.
-    inliner.performForcedInlining(
-        method, code, extraMethodCalls, inliningIRProvider, Timing.empty());
-    return true;
-  }
-
   private boolean forceInlineDirectMethodInvocations(
       IRCode code, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException {
-    if (methodCallsOnInstance.isEmpty()) {
+    if (directMethodCalls.isEmpty()) {
       return false;
     }
 
-    assert methodCallsOnInstance.keySet().stream()
-        .map(InvokeMethodWithReceiver::getReceiver)
-        .allMatch(receivers::isReceiverAlias);
-
     inliner.performForcedInlining(
-        method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+        method, code, directMethodCalls, inliningIRProvider, Timing.empty());
 
     // In case we are class inlining an object allocation that does not inherit directly from
     // java.lang.Object, we need keep force inlining the constructor until we reach
     // java.lang.Object.<init>().
     if (root.isNewInstance()) {
       do {
-        methodCallsOnInstance.clear();
+        directMethodCalls.clear();
         for (Instruction instruction : eligibleInstance.uniqueUsers()) {
           if (instruction.isInvokeDirect()) {
             InvokeDirect invoke = instruction.asInvokeDirect();
@@ -512,7 +425,7 @@
             }
 
             DexProgramClass holder =
-                asProgramClassOrNull(appView.definitionForHolder(invokedMethod));
+                asProgramClassOrNull(appView.definitionForHolder(invokedMethod, method));
             if (holder == null) {
               throw new IllegalClassInlinerStateException();
             }
@@ -523,21 +436,21 @@
                     .getDefinition()
                     .isInliningCandidate(
                         method,
-                        Reason.SIMPLE,
+                        Reason.ALWAYS,
                         appView.appInfo(),
                         NopWhyAreYouNotInliningReporter.getInstance())) {
               throw new IllegalClassInlinerStateException();
             }
 
-            methodCallsOnInstance.put(invoke, new InliningInfo(singleTarget, eligibleClass.type));
+            directMethodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass.type));
             break;
           }
         }
-        if (!methodCallsOnInstance.isEmpty()) {
+        if (!directMethodCalls.isEmpty()) {
           inliner.performForcedInlining(
-              method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+              method, code, directMethodCalls, inliningIRProvider, Timing.empty());
         }
-      } while (!methodCallsOnInstance.isEmpty());
+      } while (!directMethodCalls.isEmpty());
     }
 
     return true;
@@ -746,11 +659,24 @@
 
   // Replace field reads with appropriate values, insert phis when needed.
   private void removeFieldReads(IRCode code) {
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    if (root.isNewInstance()) {
+      removeFieldReadsFromNewInstance(code, affectedValues);
+    } else {
+      assert root.isStaticGet();
+      removeFieldReadsFromStaticGet(code, affectedValues);
+    }
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+  }
+
+  private void removeFieldReadsFromNewInstance(IRCode code, Set<Value> affectedValues) {
     TreeSet<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder =
         new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       if (user.isInstanceGet()) {
-        if (user.outValue().hasAnyUsers()) {
+        if (user.hasUsedOutValue()) {
           uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet());
         } else {
           removeInstruction(user);
@@ -761,6 +687,7 @@
       if (user.isInstancePut()) {
         // Skip in this iteration since these instructions are needed to properly calculate what
         // value should field reads be replaced with.
+        assert root.isNewInstance();
         continue;
       }
 
@@ -774,12 +701,15 @@
     Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
     for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) {
       // Replace a field read with appropriate value.
-      replaceFieldRead(code, user, fieldHelpers);
+      removeFieldReadFromNewInstance(code, user, affectedValues, fieldHelpers);
     }
   }
 
-  private void replaceFieldRead(
-      IRCode code, InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
+  private void removeFieldReadFromNewInstance(
+      IRCode code,
+      InstanceGet fieldRead,
+      Set<Value> affectedValues,
+      Map<DexField, FieldValueHelper> fieldHelpers) {
     Value value = fieldRead.outValue();
     if (value != null) {
       FieldValueHelper helper =
@@ -794,12 +724,89 @@
       // `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the
       // type of read field, but it could be more precise than that due to (multiple) inlining.
       // In addition to values affected by `newValue`, it's necessary to revisit `newValue` itself.
-      new TypeAnalysis(appView).narrowing(
-          Iterables.concat(ImmutableSet.of(newValue), newValue.affectedValues()));
+      affectedValues.add(newValue);
+      affectedValues.addAll(newValue.affectedValues());
     }
     removeInstruction(fieldRead);
   }
 
+  private void removeFieldReadsFromStaticGet(IRCode code, Set<Value> affectedValues) {
+    Set<BasicBlock> seen = Sets.newIdentityHashSet();
+    Set<Instruction> users = eligibleInstance.uniqueUsers();
+    for (Instruction user : users) {
+      BasicBlock block = user.getBlock();
+      if (!seen.add(block)) {
+        continue;
+      }
+
+      InstructionListIterator instructionIterator = block.listIterator(code);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (!users.contains(instruction)) {
+          continue;
+        }
+
+        if (user.isInstanceGet()) {
+          if (user.hasUsedOutValue()) {
+            replaceFieldReadFromStaticGet(
+                code, instructionIterator, user.asInstanceGet(), affectedValues);
+          } else {
+            instructionIterator.removeOrReplaceByDebugLocalRead();
+          }
+          continue;
+        }
+
+        if (user.isInstancePut()) {
+          instructionIterator.removeOrReplaceByDebugLocalRead();
+          continue;
+        }
+
+        throw new Unreachable(
+            "Unexpected usage left in method `"
+                + method.toSourceString()
+                + "` after inlining: "
+                + user);
+      }
+    }
+  }
+
+  private void replaceFieldReadFromStaticGet(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InstanceGet fieldRead,
+      Set<Value> affectedValues) {
+    DexField fieldReference = fieldRead.getField();
+    DexClass holder = appView.definitionFor(fieldReference.getHolderType(), method);
+    DexEncodedField field = fieldReference.lookupOnClass(holder);
+    if (field == null) {
+      throw reportUnknownFieldReadFromSingleton(fieldRead);
+    }
+
+    AbstractValue abstractValue = objectState.getAbstractFieldValue(field);
+    if (!abstractValue.isSingleConstValue()) {
+      throw reportUnknownFieldReadFromSingleton(fieldRead);
+    }
+
+    SingleConstValue singleConstValue = abstractValue.asSingleConstValue();
+    if (!singleConstValue.isMaterializableInContext(appView, method)) {
+      throw reportUnknownFieldReadFromSingleton(fieldRead);
+    }
+
+    Instruction replacement =
+        singleConstValue.createMaterializingInstruction(appView, code, fieldRead);
+    instructionIterator.replaceCurrentInstruction(replacement, affectedValues);
+  }
+
+  private RuntimeException reportUnknownFieldReadFromSingleton(InstanceGet fieldRead) {
+    throw appView
+        .reporter()
+        .fatalError(
+            "Unexpected usage left in method `"
+                + method.toSourceString()
+                + "` after inlining: "
+                + fieldRead.toString());
+  }
+
   private void removeFieldWrites() {
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       if (!user.isInstancePut()) {
@@ -809,6 +816,9 @@
                 + "` after field reads removed: "
                 + user);
       }
+
+      assert root.isNewInstance();
+
       InstancePut instancePut = user.asInstancePut();
       DexEncodedField field =
           appView
@@ -864,7 +874,8 @@
       if (parent == null) {
         return null;
       }
-      DexProgramClass parentClass = asProgramClassOrNull(appView.definitionForHolder(parent));
+      DexProgramClass parentClass =
+          asProgramClassOrNull(appView.definitionForHolder(parent, method));
       if (parentClass == null) {
         return null;
       }
@@ -878,7 +889,7 @@
       DexEncodedMethod encodedParentMethod = encodedParent.getDefinition();
       if (!encodedParentMethod.isInliningCandidate(
           method,
-          Reason.SIMPLE,
+          Reason.ALWAYS,
           appView.appInfo(),
           NopWhyAreYouNotInliningReporter.getInstance())) {
         return null;
@@ -900,11 +911,29 @@
   // - if it is a regular chaining pattern where the only users of the out value are receivers to
   //   other invocations. In that case, we should add all indirect users of the out value to ensure
   //   they can also be inlined.
-  private boolean isEligibleInvokeWithAllUsersAsReceivers(
-      ClassInlinerEligibilityInfo eligibility,
-      InvokeMethodWithReceiver invoke,
+  private boolean scheduleNewUsersForAnalysis(
+      InvokeMethod invoke,
+      ProgramMethod singleTarget,
+      int parameter,
       Set<Instruction> indirectUsers) {
-    if (eligibility.returnsReceiver.isFalse()) {
+    ClassInlinerMethodConstraint classInlinerMethodConstraint =
+        singleTarget.getDefinition().getOptimizationInfo().getClassInlinerMethodConstraint();
+    ParameterUsage usage = classInlinerMethodConstraint.getParameterUsage(parameter);
+
+    OptionalBool returnsParameter;
+    if (usage.isParameterReturned()) {
+      if (singleTarget.getDefinition().getOptimizationInfo().returnsArgument()) {
+        assert singleTarget.getDefinition().getOptimizationInfo().getReturnedArgument()
+            == parameter;
+        returnsParameter = OptionalBool.TRUE;
+      } else {
+        returnsParameter = OptionalBool.UNKNOWN;
+      }
+    } else {
+      returnsParameter = OptionalBool.FALSE;
+    }
+
+    if (returnsParameter.isFalse()) {
       return true;
     }
 
@@ -922,168 +951,58 @@
 
     // We cannot guarantee the invoke returns the receiver or another instance and since the
     // return value is used we have to bail out.
-    if (eligibility.returnsReceiver.isUnknown()) {
+    if (returnsParameter.isUnknown()) {
       return false;
     }
 
     // Add the out-value as a definite-alias if the invoke instruction is guaranteed to return the
     // receiver. Otherwise, the out-value may be an alias of the receiver, and it is added to the
     // may-alias set.
-    AliasKind kind = eligibility.returnsReceiver.isTrue() ? AliasKind.DEFINITE : AliasKind.MAYBE;
-    if (!receivers.addReceiverAlias(outValue, kind)) {
+    assert returnsParameter.isTrue();
+    if (!receivers.addReceiverAlias(outValue)) {
       return false;
     }
 
-    Set<Instruction> currentUsers = outValue.uniqueUsers();
-    while (!currentUsers.isEmpty()) {
-      Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
-      for (Instruction instruction : currentUsers) {
-        if (instruction.isAssume() || instruction.isCheckCast()) {
-          if (instruction.isCheckCast()) {
-            CheckCast checkCast = instruction.asCheckCast();
-            if (!appView.appInfo().isSubtype(eligibleClass.type, checkCast.getType())) {
-              return false; // Unsafe cast.
-            }
-          }
-          Value outValueAlias = instruction.outValue();
-          if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
-            return false;
-          }
-          if (!receivers.addReceiverAlias(outValueAlias, kind)) {
-            return false;
-          }
-          indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
-          continue;
-        }
-
-        if (instruction.isInvokeMethodWithReceiver()) {
-          InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
-          if (user.getReceiver().getAliasedValue(aliasesThroughAssumeAndCheckCasts) != outValue) {
-            return false;
-          }
-          for (int i = 1; i < user.inValues().size(); i++) {
-            Value inValue = user.inValues().get(i);
-            if (inValue.getAliasedValue(aliasesThroughAssumeAndCheckCasts) == outValue) {
-              return false;
-            }
-          }
-          indirectUsers.add(user);
-          continue;
-        }
-
-        return false;
-      }
-      currentUsers = indirectOutValueUsers;
-    }
-
+    indirectUsers.addAll(outValue.uniqueUsers());
     return true;
   }
 
-  private InliningInfo isEligibleDirectVirtualMethodCall(
-      InvokeMethodWithReceiver invoke,
-      SingleResolutionResult resolutionResult,
-      ProgramMethod singleTarget,
-      Set<Instruction> indirectUsers,
-      Supplier<InliningOracle> defaultOracle) {
-    assert isEligibleSingleTarget(singleTarget);
-
-    // None of the none-receiver arguments may be an alias of the receiver.
-    List<Value> inValues = invoke.inValues();
-    for (int i = 1; i < inValues.size(); i++) {
-      if (!receivers.addIllegalReceiverAlias(inValues.get(i))) {
-        return null;
-      }
-    }
-
-    // TODO(b/141719453): Should not constrain library overrides if all instantiations are inlined.
-    if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) {
-      InliningOracle inliningOracle = defaultOracle.get();
-      if (!inliningOracle.passesInliningConstraints(
-          invoke,
-          resolutionResult,
-          singleTarget,
-          Reason.SIMPLE,
-          NopWhyAreYouNotInliningReporter.getInstance())) {
-        return null;
-      }
-    }
-
-    if (!isEligibleVirtualMethodCall(
-        invoke,
-        invoke.getInvokedMethod(),
-        singleTarget,
-        eligibility -> isEligibleInvokeWithAllUsersAsReceivers(eligibility, invoke, indirectUsers),
-        invoke.getType())) {
-      return null;
-    }
-
-    ClassInlinerEligibilityInfo eligibility =
-        singleTarget.getDefinition().getOptimizationInfo().getClassInlinerEligibility();
-    if (eligibility.callsReceiver.size() > 1) {
-      return null;
-    }
-    if (!eligibility.callsReceiver.isEmpty()) {
-      assert eligibility.callsReceiver.get(0).getFirst() == Invoke.Type.VIRTUAL;
-      Pair<Type, DexMethod> invokeInfo = eligibility.callsReceiver.get(0);
-      Type invokeType = invokeInfo.getFirst();
-      DexMethod indirectlyInvokedMethod = invokeInfo.getSecond();
-      SingleResolutionResult indirectResolutionResult =
-          appView
-              .appInfo()
-              .resolveMethodOn(eligibleClass, indirectlyInvokedMethod)
-              .asSingleResolution();
-      if (indirectResolutionResult == null) {
-        return null;
-      }
-      ProgramMethod indirectSingleTarget =
-          indirectResolutionResult.getResolutionPair().asProgramMethod();
-      if (!isEligibleIndirectVirtualMethodCall(
-          indirectlyInvokedMethod, invokeType, indirectSingleTarget)) {
-        return null;
-      }
-      indirectMethodCallsOnInstance.add(indirectSingleTarget);
-    }
-
-    return new InliningInfo(singleTarget, eligibleClass.type);
-  }
-
-  private boolean isEligibleIndirectVirtualMethodCall(DexMethod invokedMethod, Type type) {
-    ProgramMethod singleTarget =
-        asProgramMethodOrNull(
-            appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).getSingleTarget(),
-            appView);
-    return isEligibleIndirectVirtualMethodCall(invokedMethod, type, singleTarget);
-  }
 
   private boolean isEligibleIndirectVirtualMethodCall(
-      DexMethod invokedMethod, Type type, ProgramMethod singleTarget) {
+      DexMethod invokedMethod, ProgramMethod singleTarget) {
     if (!isEligibleSingleTarget(singleTarget)) {
       return false;
     }
     if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) {
       return false;
     }
-    return isEligibleVirtualMethodCall(
-        null,
-        invokedMethod,
-        singleTarget,
-        eligibility -> eligibility.callsReceiver.isEmpty() && eligibility.returnsReceiver.isFalse(),
-        type);
+    if (!isEligibleVirtualMethodCall(invokedMethod, singleTarget)) {
+      return false;
+    }
+
+    ParameterUsage usage =
+        singleTarget
+            .getDefinition()
+            .getOptimizationInfo()
+            .getClassInlinerMethodConstraint()
+            .getParameterUsage(0);
+    assert !usage.isTop();
+    if (usage.isBottom()) {
+      return true;
+    }
+    NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty();
+    return nonEmptyUsage.getMethodCallsWithParameterAsReceiver().isEmpty()
+        && !nonEmptyUsage.isParameterReturned();
   }
 
-  private boolean isEligibleVirtualMethodCall(
-      InvokeMethodWithReceiver invoke,
-      DexMethod callee,
-      ProgramMethod singleTarget,
-      Predicate<ClassInlinerEligibilityInfo> eligibilityAcceptanceCheck,
-      Type type) {
+  private boolean isEligibleVirtualMethodCall(DexMethod callee, ProgramMethod singleTarget) {
     assert isEligibleSingleTarget(singleTarget);
 
     // We should not inline a method if the invocation has type interface or virtual and the
     // signature of the invocation resolves to a private or static method.
     // TODO(b/147212189): Why not inline private methods? If access is permitted it is valid.
     ResolutionResult resolutionResult =
-        appView.appInfo().resolveMethod(callee, type == Type.INTERFACE);
+        appView.appInfo().resolveMethodOnClass(callee, eligibleClass);
     if (resolutionResult.isSingleResolution()
         && !resolutionResult.getSingleTarget().isNonPrivateVirtualMethod()) {
       return false;
@@ -1099,53 +1018,20 @@
     MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
     ClassInlinerMethodConstraint classInlinerMethodConstraint =
         optimizationInfo.getClassInlinerMethodConstraint();
+    int parameter = 0;
     if (root.isNewInstance()) {
-      if (!classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(singleTarget)) {
-        return false;
-      }
-    } else {
-      assert root.isStaticGet();
-      if (!classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(singleTarget)) {
-        return false;
-      }
+      return classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(
+          singleTarget, parameter);
     }
 
-    // If the method returns receiver and the return value is actually
-    // used in the code we need to make some additional checks.
-    if (!eligibilityAcceptanceCheck.test(optimizationInfo.getClassInlinerEligibility())) {
-      return false;
-    }
-
-    markSizeForInlining(invoke, singleTarget);
-    return true;
-  }
-
-  private boolean isExtraMethodCall(InvokeMethod invoke) {
-    if (invoke.isInvokeDirect() && dexItemFactory.isConstructor(invoke.getInvokedMethod())) {
-      return false;
-    }
-    if (invoke.isInvokeMethodWithReceiver()) {
-      Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
-      if (!receivers.addIllegalReceiverAlias(receiver)) {
-        return false;
-      }
-    }
-    if (invoke.isInvokeSuper()) {
-      return false;
-    }
-    if (invoke.isInvokePolymorphic()) {
-      return false;
-    }
-    return true;
+    assert root.isStaticGet();
+    return classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
+        appView, parameter, objectState, method);
   }
 
   // Analyzes if a method invoke the eligible instance is passed to is eligible. In short,
   // it can be eligible if:
   //
-  //   -- eligible instance is passed as argument #N which is not used in the method,
-  //      such cases are collected in 'unusedArguments' parameter and later replaced
-  //      with 'null' value
-  //
   //   -- eligible instance is passed as argument #N which is only used in the method to
   //      call a method on this object (we call it indirect method call), and method is
   //      eligible according to the same rules defined for direct method call eligibility
@@ -1157,52 +1043,48 @@
   //
   //   -- method itself can be inlined
   //
-  private boolean isExtraMethodCallEligible(
-      InvokeMethod invoke, ProgramMethod singleTarget, Supplier<InliningOracle> defaultOracle) {
-    // Don't consider constructor invocations and super calls, since we don't want to forcibly
-    // inline them.
-    assert isExtraMethodCall(invoke);
-    assert isEligibleSingleTarget(singleTarget);
-
-    List<Value> arguments = Lists.newArrayList(invoke.inValues());
-
-    // If we got here with invocation on receiver the user is ineligible.
-    if (invoke.isInvokeMethodWithReceiver()) {
-      InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
-      Value receiver = invokeMethodWithReceiver.getReceiver();
-      if (!receivers.addIllegalReceiverAlias(receiver)) {
-        return false;
-      }
-
-      // A definitely null receiver will throw an error on call site.
-      if (receiver.getType().nullability().isDefinitelyNull()) {
-        return false;
-      }
-    }
-
-    MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
-
-    // Go through all arguments, see if all usages of eligibleInstance are good.
-    if (!isEligibleParameterUsages(
-        invoke, arguments, singleTarget.getDefinition(), defaultOracle)) {
+  private boolean isEligibleDirectMethodCall(
+      InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
+      ProgramMethod singleTarget,
+      Supplier<InliningOracle> defaultOracle,
+      Set<Instruction> indirectUsers) {
+    if (!((invoke.isInvokeDirect() && !invoke.isInvokeConstructor(dexItemFactory))
+        || invoke.isInvokeInterface()
+        || invoke.isInvokeStatic()
+        || invoke.isInvokeVirtual())) {
       return false;
     }
 
-    for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
-      Value argument = arguments.get(argIndex).getAliasedValue();
-      ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
-      if (receivers.isDefiniteReceiverAlias(argument)
-          && parameterUsage != null
-          && parameterUsage.notUsed()) {
-        // Reference can be removed since it's not used.
-        unusedArguments.add(new Pair<>(invoke, argIndex));
+    // If we got here with invocation on receiver the user is ineligible.
+    if (invoke.isInvokeMethodWithReceiver()) {
+      // A definitely null receiver will throw an error on call site.
+      Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+      if (receiver.getType().isDefinitelyNull()) {
+        return false;
       }
     }
 
-    extraMethodCalls.put(invoke, new InliningInfo(singleTarget, null));
+    // Check if the method is inline-able by standard inliner.
+    InliningOracle oracle = defaultOracle.get();
+    if (!oracle.passesInliningConstraints(
+        invoke,
+        resolutionResult,
+        singleTarget,
+        Reason.ALWAYS,
+        NopWhyAreYouNotInliningReporter.getInstance())) {
+      return false;
+    }
+
+    // Go through all arguments, see if all usages of eligibleInstance are good.
+    if (!isEligibleParameterUsages(invoke, singleTarget, indirectUsers)) {
+      return false;
+    }
+
+    directMethodCalls.put(invoke, new InliningInfo(singleTarget, null));
 
     // Looks good.
-    markSizeForInlining(invoke, singleTarget);
+    markSizeOfDirectTargetForInlining(invoke, singleTarget);
     return true;
   }
 
@@ -1219,147 +1101,90 @@
   }
 
   private boolean isEligibleParameterUsages(
-      InvokeMethod invoke,
-      List<Value> arguments,
-      DexEncodedMethod singleTarget,
-      Supplier<InliningOracle> defaultOracle) {
+      InvokeMethod invoke, ProgramMethod singleTarget, Set<Instruction> indirectUsers) {
     // Go through all arguments, see if all usages of eligibleInstance are good.
-    for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
-      MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
-      ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
-
-      Value argument = arguments.get(argIndex);
+    for (int parameter = 0; parameter < invoke.arguments().size(); parameter++) {
+      Value argument = invoke.getArgument(parameter);
       if (receivers.isReceiverAlias(argument)) {
         // Have parameter usage info?
-        if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
+        if (!isEligibleParameterUsage(invoke, singleTarget, parameter, indirectUsers)) {
           return false;
         }
       } else {
         // Nothing to worry about, unless `argument` becomes an alias of the receiver later.
+        int finalParameter = parameter;
         receivers.addDeferredAliasValidityCheck(
-            argument, () -> isEligibleParameterUsage(parameterUsage, invoke, defaultOracle));
+            argument,
+            () -> isEligibleParameterUsage(invoke, singleTarget, finalParameter, indirectUsers));
       }
     }
     return true;
   }
 
   private boolean isEligibleParameterUsage(
-      ParameterUsage parameterUsage, InvokeMethod invoke, Supplier<InliningOracle> defaultOracle) {
-    if (parameterUsage == null) {
-      return false; // Don't know anything.
-    }
-
-    if (parameterUsage.notUsed()) {
-      return true;
-    }
-
-    if (parameterUsage.isAssignedToField) {
-      return false;
-    }
-
-    if (root.isStaticGet()) {
-      // If we are class inlining a singleton instance from a static-get, then we don't know
-      // the value of the fields, and we also can't optimize away instance-field assignments, as
-      // they have global side effects.
-      if (parameterUsage.hasFieldAssignment || parameterUsage.hasFieldRead) {
+      InvokeMethod invoke,
+      ProgramMethod singleTarget,
+      int parameter,
+      Set<Instruction> indirectUsers) {
+    ClassInlinerMethodConstraint classInlinerMethodConstraint =
+        singleTarget.getDefinition().getOptimizationInfo().getClassInlinerMethodConstraint();
+    if (root.isNewInstance()) {
+      if (!classInlinerMethodConstraint.isEligibleForNewInstanceClassInlining(
+          singleTarget, parameter)) {
+        return false;
+      }
+    } else {
+      assert root.isStaticGet();
+      if (!classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
+          appView, parameter, objectState, method)) {
         return false;
       }
     }
 
-    if (parameterUsage.isReturned) {
-      if (invoke.outValue() != null && invoke.outValue().hasAnyUsers()) {
-        // Used as return value which is not ignored.
-        return false;
-      }
-    }
-
-    if (parameterUsage.isUsedInMonitor) {
-      return !root.isStaticGet();
-    }
-
-    if (!Sets.difference(parameterUsage.ifZeroTest, ALLOWED_ZERO_TEST_TYPES).isEmpty()) {
-      // Used in unsupported zero-check-if kinds.
+    ParameterUsage usage = classInlinerMethodConstraint.getParameterUsage(parameter);
+    if (!scheduleNewUsersForAnalysis(invoke, singleTarget, parameter, indirectUsers)) {
       return false;
     }
 
-    for (Pair<Type, DexMethod> call : parameterUsage.callsReceiver) {
-      Type type = call.getFirst();
-      DexMethod target = call.getSecond();
+    if (!usage.isBottom()) {
+      NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty();
+      for (DexMethod invokedMethod : nonEmptyUsage.getMethodCallsWithParameterAsReceiver()) {
+        SingleResolutionResult resolutionResult =
+            appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).asSingleResolution();
+        if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
+          return false;
+        }
 
-      if (type == Type.VIRTUAL || type == Type.INTERFACE) {
         // Is the method called indirectly still eligible?
-        if (!isEligibleIndirectVirtualMethodCall(target, type)) {
+        ProgramMethod indirectSingleTarget = resolutionResult.getResolutionPair().asProgramMethod();
+        if (!isEligibleIndirectVirtualMethodCall(invokedMethod, indirectSingleTarget)) {
           return false;
         }
-      } else if (type == Type.DIRECT) {
-        if (!isInstanceInitializerEligibleForClassInlining(target)) {
-          // Only calls to trivial instance initializers are supported at this point.
-          return false;
-        }
-      } else {
-        // Static and super calls are not yet supported.
-        return false;
-      }
-
-      SingleResolutionResult resolutionResult =
-          appView
-              .appInfo()
-              .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
-              .asSingleResolution();
-      if (resolutionResult == null) {
-        return false;
-      }
-
-      // Check if the method is inline-able by standard inliner.
-      // TODO(b/156853206): Should not duplicate resolution.
-      ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, method);
-      if (singleTarget == null) {
-        return false;
-      }
-
-      InliningOracle oracle = defaultOracle.get();
-      InlineAction inlineAction =
-          oracle.computeInlining(
-              invoke,
-              resolutionResult,
-              singleTarget,
-              method,
-              ClassInitializationAnalysis.trivial(),
-              NopWhyAreYouNotInliningReporter.getInstance());
-      if (inlineAction == null) {
-        return false;
+        markSizeOfIndirectTargetForInlining(indirectSingleTarget);
       }
     }
+
     return true;
   }
 
-  private boolean isInstanceInitializerEligibleForClassInlining(DexMethod method) {
-    if (method == dexItemFactory.objectMembers.constructor) {
-      return true;
-    }
-    DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method));
-    DexEncodedMethod definition = method.lookupOnClass(holder);
-    if (definition == null) {
-      return false;
-    }
-    InstanceInitializerInfo initializerInfo =
-        definition.getOptimizationInfo().getContextInsensitiveInstanceInitializerInfo();
-    return initializerInfo.receiverNeverEscapesOutsideConstructorChain();
-  }
-
   private boolean exemptFromInstructionLimit(ProgramMethod inlinee) {
     KotlinClassLevelInfo kotlinInfo = inlinee.getHolder().getKotlinInfo();
     return kotlinInfo.isSyntheticClass() && kotlinInfo.asSyntheticClass().isLambda();
   }
 
-  private void markSizeForInlining(InvokeMethod invoke, ProgramMethod inlinee) {
+  private void markSizeOfIndirectTargetForInlining(ProgramMethod inlinee) {
     assert !methodProcessor.isProcessedConcurrently(inlinee);
     if (!exemptFromInstructionLimit(inlinee)) {
-      if (invoke != null) {
-        directInlinees.put(invoke, inlinee);
-      } else {
-        indirectInlinees.add(inlinee);
-      }
+      indirectInlinees.add(inlinee);
+    }
+    indirectMethodCallsOnInstance.add(inlinee);
+  }
+
+  private void markSizeOfDirectTargetForInlining(InvokeMethod invoke, ProgramMethod inlinee) {
+    assert invoke != null;
+    assert !methodProcessor.isProcessedConcurrently(inlinee);
+    if (!exemptFromInstructionLimit(inlinee)) {
+      directInlinees.put(invoke, inlinee);
     }
   }
 
@@ -1374,7 +1199,7 @@
         .getDefinition()
         .isInliningCandidate(
             method,
-            Reason.SIMPLE,
+            Reason.ALWAYS,
             appView.appInfo(),
             NopWhyAreYouNotInliningReporter.getInstance())) {
       // If `singleTarget` is not an inlining candidate, we won't be able to inline it here.
@@ -1394,5 +1219,10 @@
     instruction.getBlock().removeInstruction(instruction);
   }
 
-  static class IllegalClassInlinerStateException extends Exception {}
+  static class IllegalClassInlinerStateException extends Exception {
+
+    IllegalClassInlinerStateException() {
+      assert false;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java
new file mode 100644
index 0000000..5f63f4c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/AnalysisContext.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+/**
+ * The context used for the {@link ParameterUsages} lattice.
+ *
+ * <p>By only using {@link DefaultAnalysisContext} a context-insensitive result is achieved.
+ * Context-sensitive results can be achieved by forking different analysis contexts during the class
+ * inliner constraint analysis.
+ */
+public abstract class AnalysisContext {
+
+  public static DefaultAnalysisContext getDefaultContext() {
+    return DefaultAnalysisContext.getInstance();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
new file mode 100644
index 0000000..930edc5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsage.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+
+class BottomParameterUsage extends ParameterUsage {
+
+  private static final BottomParameterUsage BOTTOM = new BottomParameterUsage();
+
+  private BottomParameterUsage() {}
+
+  static BottomParameterUsage getInstance() {
+    return BOTTOM;
+  }
+
+  @Override
+  ParameterUsage addFieldReadFromParameter(DexField field) {
+    return InternalNonEmptyParameterUsage.builder().addFieldReadFromParameter(field).build();
+  }
+
+  @Override
+  ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) {
+    return InternalNonEmptyParameterUsage.builder()
+        .addMethodCallWithParameterAsReceiver(invoke)
+        .build();
+  }
+
+  @Override
+  ParameterUsage externalize() {
+    return this;
+  }
+
+  @Override
+  public boolean isBottom() {
+    return true;
+  }
+
+  @Override
+  public boolean isParameterMutated() {
+    return false;
+  }
+
+  @Override
+  public boolean isParameterReturned() {
+    return false;
+  }
+
+  @Override
+  public boolean isParameterUsedAsLock() {
+    return false;
+  }
+
+  @Override
+  ParameterUsage setParameterMutated() {
+    return InternalNonEmptyParameterUsage.builder().setParameterMutated().build();
+  }
+
+  @Override
+  ParameterUsage setParameterReturned() {
+    return InternalNonEmptyParameterUsage.builder().setParameterReturned().build();
+  }
+
+  @Override
+  ParameterUsage setParameterUsedAsLock() {
+    return InternalNonEmptyParameterUsage.builder().setParameterUsedAsLock().build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java
new file mode 100644
index 0000000..b535038
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsagePerContext.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import java.util.function.BiFunction;
+
+class BottomParameterUsagePerContext extends ParameterUsagePerContext {
+
+  private static final BottomParameterUsagePerContext INSTANCE =
+      new BottomParameterUsagePerContext();
+
+  private BottomParameterUsagePerContext() {}
+
+  static BottomParameterUsagePerContext getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  ParameterUsagePerContext externalize() {
+    return this;
+  }
+
+  @Override
+  public ParameterUsage get(AnalysisContext context) {
+    return ParameterUsage.bottom();
+  }
+
+  @Override
+  public boolean isBottom() {
+    return true;
+  }
+
+  @Override
+  ParameterUsagePerContext rebuild(
+      BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
new file mode 100644
index 0000000..84bae0e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+public class BottomParameterUsages extends ParameterUsages {
+
+  private static final BottomParameterUsages INSTANCE = new BottomParameterUsages();
+
+  private BottomParameterUsages() {}
+
+  static BottomParameterUsages getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  ParameterUsages externalize() {
+    return this;
+  }
+
+  @Override
+  public ParameterUsagePerContext get(int parameter) {
+    return ParameterUsagePerContext.bottom();
+  }
+
+  @Override
+  public boolean isBottom() {
+    return true;
+  }
+
+  @Override
+  ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext) {
+    Int2ObjectOpenHashMap<ParameterUsagePerContext> backing = new Int2ObjectOpenHashMap<>();
+    backing.put(parameter, usagePerContext);
+    return NonEmptyParameterUsages.create(backing);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return other == INSTANCE;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
new file mode 100644
index 0000000..b8f406a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
+import com.android.tools.r8.ir.optimize.classinliner.constraint.ConditionalClassInlinerMethodConstraint;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ClassInlinerMethodConstraintAnalysis {
+
+  public static ClassInlinerMethodConstraint analyze(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) {
+    if (method.getDefinition().isClassInitializer()
+        || method.getDefinition().getNumberOfArguments() == 0) {
+      return ClassInlinerMethodConstraint.alwaysFalse();
+    }
+
+    // Analyze code.
+    IntraproceduralDataflowAnalysis<ParameterUsages> analysis =
+        new IntraproceduralDataflowAnalysis<>(
+            ParameterUsages.bottom(), new TransferFunction(appView, method, code));
+    SuccessfulDataflowAnalysisResult<ParameterUsages> result =
+        analysis.run(code.entryBlock()).asSuccessfulAnalysisResult();
+    if (result == null) {
+      return ClassInlinerMethodConstraint.alwaysFalse();
+    }
+    ParameterUsages usages = result.join().externalize();
+    if (usages.isBottom()) {
+      return ClassInlinerMethodConstraint.alwaysTrue();
+    }
+    if (usages.isTop()) {
+      return ClassInlinerMethodConstraint.alwaysFalse();
+    }
+    return new ConditionalClassInlinerMethodConstraint(usages);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java
new file mode 100644
index 0000000..5378648
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/DefaultAnalysisContext.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+class DefaultAnalysisContext extends AnalysisContext {
+
+  private static final DefaultAnalysisContext INSTANCE = new DefaultAnalysisContext();
+
+  private DefaultAnalysisContext() {}
+
+  static DefaultAnalysisContext getInstance() {
+    return INSTANCE;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
new file mode 100644
index 0000000..4bba181
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/InternalNonEmptyParameterUsage.java
@@ -0,0 +1,282 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Non-trivial information (neither BOTTOM nor TOP) about a method's usage of a given parameter.
+ *
+ * <p>This is internal to the analysis, since {@link #methodCallsWithParameterAsReceiver} references
+ * instructions from {@link com.android.tools.r8.ir.code.IRCode}, which makes it unsuited for being
+ * stored in {@link com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}.
+ */
+public class InternalNonEmptyParameterUsage extends ParameterUsage {
+
+  private Set<DexField> fieldsReadFromParameter;
+  private Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver;
+
+  private boolean isParameterMutated;
+  private boolean isParameterReturned;
+  private boolean isParameterUsedAsLock;
+
+  InternalNonEmptyParameterUsage(
+      Set<DexField> fieldsReadFromParameter,
+      Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver,
+      boolean isParameterMutated,
+      boolean isParameterReturned,
+      boolean isParameterUsedAsLock) {
+    assert !fieldsReadFromParameter.isEmpty()
+        || !methodCallsWithParameterAsReceiver.isEmpty()
+        || isParameterMutated
+        || isParameterReturned
+        || isParameterUsedAsLock;
+    this.fieldsReadFromParameter = fieldsReadFromParameter;
+    this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver;
+    this.isParameterMutated = isParameterMutated;
+    this.isParameterReturned = isParameterReturned;
+    this.isParameterUsedAsLock = isParameterUsedAsLock;
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  Builder builderFromInstance() {
+    return new Builder(this);
+  }
+
+  @Override
+  InternalNonEmptyParameterUsage addFieldReadFromParameter(DexField field) {
+    ImmutableSet.Builder<DexField> newFieldsReadFromParameterBuilder = ImmutableSet.builder();
+    newFieldsReadFromParameterBuilder.addAll(fieldsReadFromParameter);
+    newFieldsReadFromParameterBuilder.add(field);
+    return new InternalNonEmptyParameterUsage(
+        newFieldsReadFromParameterBuilder.build(),
+        methodCallsWithParameterAsReceiver,
+        isParameterMutated,
+        isParameterReturned,
+        isParameterUsedAsLock);
+  }
+
+  @Override
+  InternalNonEmptyParameterUsage addMethodCallWithParameterAsReceiver(
+      InvokeMethodWithReceiver invoke) {
+    ImmutableSet.Builder<InvokeMethodWithReceiver> newMethodCallsWithParameterAsReceiverBuilder =
+        ImmutableSet.builder();
+    newMethodCallsWithParameterAsReceiverBuilder.addAll(methodCallsWithParameterAsReceiver);
+    newMethodCallsWithParameterAsReceiverBuilder.add(invoke);
+    return new InternalNonEmptyParameterUsage(
+        fieldsReadFromParameter,
+        newMethodCallsWithParameterAsReceiverBuilder.build(),
+        isParameterMutated,
+        isParameterReturned,
+        isParameterUsedAsLock);
+  }
+
+  @Override
+  public InternalNonEmptyParameterUsage asInternalNonEmpty() {
+    return this;
+  }
+
+  @Override
+  ParameterUsage externalize() {
+    ImmutableMultiset.Builder<DexMethod> methodCallsWithParameterAsReceiverBuilder =
+        ImmutableMultiset.builder();
+    methodCallsWithParameterAsReceiver.forEach(
+        invoke -> methodCallsWithParameterAsReceiverBuilder.add(invoke.getInvokedMethod()));
+    return new NonEmptyParameterUsage(
+        fieldsReadFromParameter,
+        methodCallsWithParameterAsReceiverBuilder.build(),
+        isParameterMutated,
+        isParameterReturned,
+        isParameterUsedAsLock);
+  }
+
+  @Override
+  public boolean isParameterMutated() {
+    return isParameterMutated;
+  }
+
+  @Override
+  public boolean isParameterReturned() {
+    return isParameterReturned;
+  }
+
+  @Override
+  public boolean isParameterUsedAsLock() {
+    return isParameterUsedAsLock;
+  }
+
+  InternalNonEmptyParameterUsage join(InternalNonEmptyParameterUsage other) {
+    return builderFromInstance()
+        .addFieldsReadFromParameter(other.fieldsReadFromParameter)
+        .addMethodCallsWithParameterAsReceiver(other.methodCallsWithParameterAsReceiver)
+        .joinIsReceiverMutated(other.isParameterMutated)
+        .joinIsReceiverReturned(other.isParameterReturned)
+        .joinIsReceiverUsedAsLock(other.isParameterUsedAsLock)
+        .build();
+  }
+
+  @Override
+  InternalNonEmptyParameterUsage setParameterMutated() {
+    if (isParameterMutated) {
+      return this;
+    }
+    return new InternalNonEmptyParameterUsage(
+        fieldsReadFromParameter,
+        methodCallsWithParameterAsReceiver,
+        true,
+        isParameterReturned,
+        isParameterUsedAsLock);
+  }
+
+  @Override
+  InternalNonEmptyParameterUsage setParameterReturned() {
+    if (isParameterReturned) {
+      return this;
+    }
+    return new InternalNonEmptyParameterUsage(
+        fieldsReadFromParameter,
+        methodCallsWithParameterAsReceiver,
+        isParameterMutated,
+        true,
+        isParameterUsedAsLock);
+  }
+
+  @Override
+  InternalNonEmptyParameterUsage setParameterUsedAsLock() {
+    if (isParameterUsedAsLock) {
+      return this;
+    }
+    return new InternalNonEmptyParameterUsage(
+        fieldsReadFromParameter,
+        methodCallsWithParameterAsReceiver,
+        isParameterMutated,
+        isParameterReturned,
+        true);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null || obj.getClass() != getClass()) {
+      return false;
+    }
+    InternalNonEmptyParameterUsage knownParameterUsage = (InternalNonEmptyParameterUsage) obj;
+    return isParameterMutated == knownParameterUsage.isParameterMutated
+        && isParameterReturned == knownParameterUsage.isParameterReturned
+        && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock
+        && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter)
+        && methodCallsWithParameterAsReceiver.equals(
+            knownParameterUsage.methodCallsWithParameterAsReceiver);
+  }
+
+  @Override
+  public int hashCode() {
+    int hash =
+        31 * (31 + fieldsReadFromParameter.hashCode())
+            + methodCallsWithParameterAsReceiver.hashCode();
+    assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
+    hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated);
+    hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned);
+    hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock);
+    return hash;
+  }
+
+  static class Builder {
+
+    private ImmutableSet.Builder<DexField> fieldsReadFromParameterBuilder;
+    private ImmutableSet.Builder<InvokeMethodWithReceiver>
+        methodCallsWithParameterAsReceiverBuilder;
+    private boolean isParameterMutated;
+    private boolean isParameterReturned;
+    private boolean isParameterUsedAsLock;
+
+    Builder() {
+      fieldsReadFromParameterBuilder = ImmutableSet.builder();
+      methodCallsWithParameterAsReceiverBuilder = ImmutableSet.builder();
+    }
+
+    Builder(InternalNonEmptyParameterUsage methodBehavior) {
+      fieldsReadFromParameterBuilder =
+          ImmutableSet.<DexField>builder().addAll(methodBehavior.fieldsReadFromParameter);
+      methodCallsWithParameterAsReceiverBuilder =
+          ImmutableSet.<InvokeMethodWithReceiver>builder()
+              .addAll(methodBehavior.methodCallsWithParameterAsReceiver);
+      isParameterMutated = methodBehavior.isParameterMutated;
+      isParameterReturned = methodBehavior.isParameterReturned;
+      isParameterUsedAsLock = methodBehavior.isParameterUsedAsLock;
+    }
+
+    Builder addFieldReadFromParameter(DexField fieldReadFromParameter) {
+      fieldsReadFromParameterBuilder.add(fieldReadFromParameter);
+      return this;
+    }
+
+    Builder addFieldsReadFromParameter(Collection<DexField> fieldsReadFromParameter) {
+      fieldsReadFromParameterBuilder.addAll(fieldsReadFromParameter);
+      return this;
+    }
+
+    Builder addMethodCallWithParameterAsReceiver(
+        InvokeMethodWithReceiver methodCallWithParameterAsReceiver) {
+      methodCallsWithParameterAsReceiverBuilder.add(methodCallWithParameterAsReceiver);
+      return this;
+    }
+
+    Builder addMethodCallsWithParameterAsReceiver(
+        Set<InvokeMethodWithReceiver> methodCallsWithParameterAsReceiver) {
+      methodCallsWithParameterAsReceiverBuilder.addAll(methodCallsWithParameterAsReceiver);
+      return this;
+    }
+
+    Builder joinIsReceiverMutated(boolean isParameterMutated) {
+      this.isParameterMutated |= isParameterMutated;
+      return this;
+    }
+
+    Builder joinIsReceiverReturned(boolean isParameterReturned) {
+      this.isParameterReturned |= isParameterReturned;
+      return this;
+    }
+
+    Builder joinIsReceiverUsedAsLock(boolean isParameterUsedAsLock) {
+      this.isParameterUsedAsLock |= isParameterUsedAsLock;
+      return this;
+    }
+
+    Builder setParameterMutated() {
+      this.isParameterMutated = true;
+      return this;
+    }
+
+    Builder setParameterReturned() {
+      this.isParameterReturned = true;
+      return this;
+    }
+
+    Builder setParameterUsedAsLock() {
+      this.isParameterUsedAsLock = true;
+      return this;
+    }
+
+    InternalNonEmptyParameterUsage build() {
+      return new InternalNonEmptyParameterUsage(
+          fieldsReadFromParameterBuilder.build(),
+          methodCallsWithParameterAsReceiverBuilder.build(),
+          isParameterMutated,
+          isParameterReturned,
+          isParameterUsedAsLock);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
new file mode 100644
index 0000000..d377305
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsage.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.Multiset;
+import java.util.Objects;
+import java.util.Set;
+
+public class NonEmptyParameterUsage extends ParameterUsage {
+
+  private Set<DexField> fieldsReadFromParameter;
+  private Multiset<DexMethod> methodCallsWithParameterAsReceiver;
+
+  private boolean isParameterMutated;
+  private boolean isParameterReturned;
+  private boolean isParameterUsedAsLock;
+
+  NonEmptyParameterUsage(
+      Set<DexField> fieldsReadFromParameter,
+      Multiset<DexMethod> methodCallsWithParameterAsReceiver,
+      boolean isParameterMutated,
+      boolean isParameterReturned,
+      boolean isParameterUsedAsLock) {
+    assert !fieldsReadFromParameter.isEmpty()
+        || !methodCallsWithParameterAsReceiver.isEmpty()
+        || isParameterMutated
+        || isParameterReturned
+        || isParameterUsedAsLock;
+    this.fieldsReadFromParameter = fieldsReadFromParameter;
+    this.methodCallsWithParameterAsReceiver = methodCallsWithParameterAsReceiver;
+    this.isParameterMutated = isParameterMutated;
+    this.isParameterReturned = isParameterReturned;
+    this.isParameterUsedAsLock = isParameterUsedAsLock;
+  }
+
+  @Override
+  ParameterUsage addFieldReadFromParameter(DexField field) {
+    throw new Unreachable();
+  }
+
+  @Override
+  ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public NonEmptyParameterUsage asNonEmpty() {
+    return this;
+  }
+
+  @Override
+  ParameterUsage externalize() {
+    throw new Unreachable();
+  }
+
+  public boolean hasFieldsReadFromParameter() {
+    return !getFieldsReadFromParameter().isEmpty();
+  }
+
+  public Set<DexField> getFieldsReadFromParameter() {
+    return fieldsReadFromParameter;
+  }
+
+  public Multiset<DexMethod> getMethodCallsWithParameterAsReceiver() {
+    return methodCallsWithParameterAsReceiver;
+  }
+
+  @Override
+  public boolean isParameterMutated() {
+    return isParameterMutated;
+  }
+
+  @Override
+  public boolean isParameterReturned() {
+    return isParameterReturned;
+  }
+
+  @Override
+  public boolean isParameterUsedAsLock() {
+    return isParameterUsedAsLock;
+  }
+
+  @Override
+  ParameterUsage setParameterMutated() {
+    throw new Unreachable();
+  }
+
+  @Override
+  ParameterUsage setParameterReturned() {
+    throw new Unreachable();
+  }
+
+  @Override
+  ParameterUsage setParameterUsedAsLock() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null || obj.getClass() != getClass()) {
+      return false;
+    }
+    NonEmptyParameterUsage knownParameterUsage = (NonEmptyParameterUsage) obj;
+    return isParameterMutated == knownParameterUsage.isParameterMutated
+        && isParameterReturned == knownParameterUsage.isParameterReturned
+        && isParameterUsedAsLock == knownParameterUsage.isParameterUsedAsLock
+        && fieldsReadFromParameter.equals(knownParameterUsage.fieldsReadFromParameter)
+        && methodCallsWithParameterAsReceiver.equals(
+            knownParameterUsage.methodCallsWithParameterAsReceiver);
+  }
+
+  @Override
+  public int hashCode() {
+    int hash =
+        31 * (31 + fieldsReadFromParameter.hashCode())
+            + methodCallsWithParameterAsReceiver.hashCode();
+    assert hash == Objects.hash(fieldsReadFromParameter, methodCallsWithParameterAsReceiver);
+    hash = (hash << 1) | BooleanUtils.intValue(isParameterMutated);
+    hash = (hash << 1) | BooleanUtils.intValue(isParameterReturned);
+    hash = (hash << 1) | BooleanUtils.intValue(isParameterUsedAsLock);
+    return hash;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
new file mode 100644
index 0000000..9460e7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
@@ -0,0 +1,131 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+class NonEmptyParameterUsagePerContext extends ParameterUsagePerContext {
+
+  private final Map<AnalysisContext, ParameterUsage> backing;
+
+  private NonEmptyParameterUsagePerContext(Map<AnalysisContext, ParameterUsage> backing) {
+    assert !backing.isEmpty();
+    this.backing = backing;
+  }
+
+  static ParameterUsagePerContext create(Map<AnalysisContext, ParameterUsage> backing) {
+    return backing.isEmpty() ? bottom() : new NonEmptyParameterUsagePerContext(backing);
+  }
+
+  static NonEmptyParameterUsagePerContext createInitial() {
+    return new NonEmptyParameterUsagePerContext(
+        ImmutableMap.of(DefaultAnalysisContext.getInstance(), ParameterUsage.bottom()));
+  }
+
+  void forEach(BiConsumer<AnalysisContext, ParameterUsage> consumer) {
+    backing.forEach(consumer);
+  }
+
+  ParameterUsagePerContext join(NonEmptyParameterUsagePerContext parameterUsagePerContext) {
+    if (isBottom()) {
+      return parameterUsagePerContext;
+    }
+    if (parameterUsagePerContext.isBottom()) {
+      return this;
+    }
+    Map<AnalysisContext, ParameterUsage> newBacking = new HashMap<>(backing);
+    parameterUsagePerContext.forEach(
+        (context, parameterUsage) ->
+            newBacking.put(
+                context,
+                parameterUsage.join(newBacking.getOrDefault(context, ParameterUsage.bottom()))));
+    return create(newBacking);
+  }
+
+  @Override
+  NonEmptyParameterUsagePerContext asKnown() {
+    return this;
+  }
+
+  @Override
+  ParameterUsagePerContext externalize() {
+    boolean allBottom = true;
+    boolean allTop = true;
+    for (ParameterUsage usage : backing.values()) {
+      if (!usage.isBottom()) {
+        allBottom = false;
+      }
+      if (!usage.isTop()) {
+        allTop = false;
+      }
+    }
+    if (allBottom) {
+      return bottom();
+    }
+    if (allTop) {
+      return top();
+    }
+    // Remove mappings to top. These mappings represent unknown information, which there is no point
+    // in storing. After the removal of these mappings, the result should still be non-empty.
+    ParameterUsagePerContext rebuilt =
+        rebuild((context, usage) -> usage.isTop() ? null : usage.externalize());
+    assert !rebuilt.isBottom();
+    assert !rebuilt.isTop();
+    return rebuilt;
+  }
+
+  @Override
+  public ParameterUsage get(AnalysisContext context) {
+    return backing.getOrDefault(context, ParameterUsage.top());
+  }
+
+  @Override
+  ParameterUsagePerContext rebuild(
+      BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+    ImmutableMap.Builder<AnalysisContext, ParameterUsage> builder = null;
+    for (Map.Entry<AnalysisContext, ParameterUsage> entry : backing.entrySet()) {
+      AnalysisContext context = entry.getKey();
+      ParameterUsage usage = entry.getValue();
+      ParameterUsage newUsage = transformation.apply(context, usage);
+      if (newUsage != null) {
+        if (newUsage != usage) {
+          if (builder == null) {
+            builder = ImmutableMap.builder();
+            for (Map.Entry<AnalysisContext, ParameterUsage> previousEntry : backing.entrySet()) {
+              AnalysisContext previousContext = previousEntry.getKey();
+              if (previousContext == context) {
+                break;
+              }
+              builder.put(previousContext, previousEntry.getValue());
+            }
+          }
+          builder.put(context, newUsage);
+        } else if (builder != null) {
+          builder.put(context, newUsage);
+        }
+      }
+    }
+    return builder != null ? create(builder.build()) : this;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null || obj.getClass() != getClass()) {
+      return false;
+    }
+    NonEmptyParameterUsagePerContext knownParameterUsagePerContext =
+        (NonEmptyParameterUsagePerContext) obj;
+    return backing.equals(knownParameterUsagePerContext.backing);
+  }
+
+  @Override
+  public int hashCode() {
+    return backing.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
new file mode 100644
index 0000000..f7b2cae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
@@ -0,0 +1,238 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Int2ObjectMapUtils;
+import com.android.tools.r8.utils.IntObjConsumer;
+import com.android.tools.r8.utils.IntObjPredicate;
+import com.android.tools.r8.utils.IntObjToObjFunction;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+/**
+ * This implements the lattice for the dataflow analysis used to determine if a given method is
+ * eligible for class inlining.
+ *
+ * <p>Given a value that is subject to class inlining (e.g., `A a = new A()`), we need to determine
+ * if the value `a` will be eligible for class inlining if it flows into a method call. The value
+ * `a` may flow into any argument position, therefore we need to be able to determine if calls where
+ * `a` is used as a receiver are eligible for class inlining {@code a.foo(...)}, as well as calls
+ * where `a` is not used as a receiver: {@code other.foo(a)} or {@code Clazz.foo(a)}.
+ *
+ * <p>To answer such questions, this lattice contains information about the way a method uses its
+ * parameters. For a given parameter, this information is encoded in {@link ParameterUsage}.
+ *
+ * <p>To facilitate context sensitive information, {@link ParameterUsagePerContext} gives the
+ * parameter usage information for a given parameter in a given context. As a simple example,
+ * consider the following method:
+ *
+ * <pre>
+ *   int x;
+ *   void foo() {
+ *     if (this.x == 0) {
+ *       // Do nothing.
+ *     } else {
+ *       System.out.println(x);
+ *     }
+ *   }
+ * </pre>
+ *
+ * <p>In the above example, the parameter `this` is not eligible for class inlining if `this.x !=
+ * 0`. However, when `this.x == 0`, the parameter usage information is bottom. This piece of
+ * information is encoded as a map lattice from contexts to parameter usage information:
+ *
+ * <pre>
+ *   ParameterUsagePerContext[
+ *       Context[this.x == 0]  ->  BottomParameterUsage  (BOTTOM),
+ *       DefaultContext        ->  UnknownParameterUsage (TOP)
+ *   ]
+ * </pre>
+ *
+ * <p>Finally, to provide the information for each method parameter, this class provides a mapping
+ * from parameters to {@link ParameterUsagePerContext}.
+ */
+public class NonEmptyParameterUsages extends ParameterUsages {
+
+  private static final AssumeAndCheckCastAliasedValueConfiguration aliasedValueConfiguration =
+      AssumeAndCheckCastAliasedValueConfiguration.getInstance();
+
+  private final Int2ObjectMap<ParameterUsagePerContext> backing;
+
+  private NonEmptyParameterUsages(Int2ObjectMap<ParameterUsagePerContext> backing) {
+    assert !backing.isEmpty() : "Should use bottom() instead";
+    this.backing = backing;
+  }
+
+  public static ParameterUsages create(Int2ObjectMap<ParameterUsagePerContext> backing) {
+    return backing.isEmpty() ? bottom() : new NonEmptyParameterUsages(backing);
+  }
+
+  public boolean allMatch(IntObjPredicate<ParameterUsagePerContext> predicate) {
+    for (Int2ObjectMap.Entry<ParameterUsagePerContext> entry : backing.int2ObjectEntrySet()) {
+      if (!predicate.test(entry.getIntKey(), entry.getValue())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public NonEmptyParameterUsages asNonEmpty() {
+    return this;
+  }
+
+  @Override
+  ParameterUsages externalize() {
+    NonEmptyParameterUsages rebuilt =
+        rebuildParameters((parameter, usagePerContext) -> usagePerContext.externalize());
+    boolean allBottom = true;
+    boolean allTop = true;
+    for (ParameterUsagePerContext usagePerContext : rebuilt.backing.values()) {
+      if (!usagePerContext.isBottom()) {
+        allBottom = false;
+      }
+      if (!usagePerContext.isTop()) {
+        allTop = false;
+      }
+    }
+    if (allBottom) {
+      return bottom();
+    }
+    if (allTop) {
+      return top();
+    }
+    return rebuilt;
+  }
+
+  @Override
+  ParameterUsages put(int parameter, ParameterUsagePerContext parameterUsagePerContext) {
+    Int2ObjectOpenHashMap<ParameterUsagePerContext> newBacking =
+        new Int2ObjectOpenHashMap<>(backing);
+    newBacking.put(parameter, parameterUsagePerContext);
+    return create(newBacking);
+  }
+
+  public void forEach(IntObjConsumer<ParameterUsagePerContext> consumer) {
+    Int2ObjectMapUtils.forEach(backing, consumer);
+  }
+
+  @Override
+  public ParameterUsagePerContext get(int parameter) {
+    return backing.getOrDefault(parameter, ParameterUsagePerContext.top());
+  }
+
+  NonEmptyParameterUsages abandonClassInliningInCurrentContexts(Value value) {
+    return rebuildParameter(value, (context, usage) -> ParameterUsage.top());
+  }
+
+  NonEmptyParameterUsages abandonClassInliningInCurrentContexts(Collection<Value> values) {
+    if (values.isEmpty()) {
+      return this;
+    }
+    int[] parametersToRebuild = new int[values.size()];
+    Iterator<Value> iterator = values.iterator();
+    for (int i = 0; i < values.size(); i++) {
+      parametersToRebuild[i] = iterator.next().getDefinition().asArgument().getIndex();
+    }
+    return rebuildParameters(
+        (currentParameter, usagePerContext) ->
+            ArrayUtils.containsInt(parametersToRebuild, currentParameter)
+                ? usagePerContext.rebuild((context, usage) -> ParameterUsage.top())
+                : usagePerContext);
+  }
+
+  NonEmptyParameterUsages abandonClassInliningInCurrentContexts(
+      Iterable<Value> values, Predicate<Value> predicate) {
+    List<Value> filtered = new ArrayList<>();
+    for (Value value : values) {
+      Value root = value.getAliasedValue(aliasedValueConfiguration);
+      if (predicate.test(root)) {
+        filtered.add(root);
+      }
+    }
+    return abandonClassInliningInCurrentContexts(filtered);
+  }
+
+  NonEmptyParameterUsages rebuildParameter(
+      Value value, BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+    Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
+    assert valueRoot.isArgument();
+    int parameter = valueRoot.getDefinition().asArgument().getIndex();
+    return rebuildParameters(
+        (currentParameter, usagePerContext) ->
+            currentParameter == parameter
+                ? usagePerContext.rebuild(transformation)
+                : usagePerContext);
+  }
+
+  NonEmptyParameterUsages rebuildParameters(
+      IntObjToObjFunction<ParameterUsagePerContext, ParameterUsagePerContext> transformation) {
+    Int2ObjectMap<ParameterUsagePerContext> rebuiltBacking = null;
+    for (Int2ObjectMap.Entry<ParameterUsagePerContext> entry : backing.int2ObjectEntrySet()) {
+      int parameter = entry.getIntKey();
+      ParameterUsagePerContext usagePerContext = entry.getValue();
+      ParameterUsagePerContext newUsagePerContext =
+          transformation.apply(parameter, entry.getValue());
+      if (newUsagePerContext != usagePerContext) {
+        if (rebuiltBacking == null) {
+          rebuiltBacking = new Int2ObjectOpenHashMap<>();
+          for (Int2ObjectMap.Entry<ParameterUsagePerContext> previousEntry :
+              backing.int2ObjectEntrySet()) {
+            int previousParameter = previousEntry.getIntKey();
+            if (previousParameter == parameter) {
+              break;
+            }
+            rebuiltBacking.put(previousParameter, previousEntry.getValue());
+          }
+        }
+        rebuiltBacking.put(parameter, newUsagePerContext);
+      } else if (rebuiltBacking != null) {
+        rebuiltBacking.put(parameter, newUsagePerContext);
+      }
+    }
+    return rebuiltBacking != null ? new NonEmptyParameterUsages(rebuiltBacking) : this;
+  }
+
+  public NonEmptyParameterUsages join(NonEmptyParameterUsages otherAnalysisState) {
+    if (isBottom()) {
+      return otherAnalysisState;
+    }
+    if (otherAnalysisState.isBottom()) {
+      return this;
+    }
+    Int2ObjectMap<ParameterUsagePerContext> newBacking = new Int2ObjectOpenHashMap<>(backing);
+    otherAnalysisState.forEach(
+        (parameter, parameterUsagePerContext) ->
+            newBacking.put(
+                parameter,
+                parameterUsagePerContext.join(
+                    Int2ObjectMapUtils.getOrDefault(
+                        newBacking, parameter, ParameterUsagePerContext.bottom()))));
+    return new NonEmptyParameterUsages(newBacking);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    NonEmptyParameterUsages analysisState = (NonEmptyParameterUsages) other;
+    return backing.equals(analysisState.backing);
+  }
+
+  @Override
+  public int hashCode() {
+    return backing.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
new file mode 100644
index 0000000..e2ad8da
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsage.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+
+public abstract class ParameterUsage {
+
+  abstract ParameterUsage addFieldReadFromParameter(DexField field);
+
+  abstract ParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke);
+
+  public NonEmptyParameterUsage asNonEmpty() {
+    return null;
+  }
+
+  InternalNonEmptyParameterUsage asInternalNonEmpty() {
+    return null;
+  }
+
+  abstract ParameterUsage externalize();
+
+  /**
+   * Returns true if this is an instanceof {@link BottomParameterUsage}.
+   *
+   * <p>Note that this does NOT imply that the parameter is <i>unused</i>, but only that it is
+   * always eligible for class inlining.
+   */
+  public boolean isBottom() {
+    return false;
+  }
+
+  /**
+   * Returns true if the method may mutate the state of this parameter (i.e., mutate the value of
+   * one of its instance fields).
+   */
+  public abstract boolean isParameterMutated();
+
+  /**
+   * Returns true if the method <i>may</i> return the parameter.
+   *
+   * <p>Note that this does NOT imply that the method <i>always</i> returns the method.
+   */
+  public abstract boolean isParameterReturned();
+
+  /**
+   * Returns true if the parameter may be used as a lock (i.e., may flow into a monitor
+   * instruction).
+   */
+  public abstract boolean isParameterUsedAsLock();
+
+  /**
+   * Returns true if this is an instance of {@link UnknownParameterUsage}.
+   *
+   * <p>In this case, the parameter is never eligible for class inlining.
+   */
+  public boolean isTop() {
+    return false;
+  }
+
+  ParameterUsage join(ParameterUsage parameterUsage) {
+    if (isBottom()) {
+      return parameterUsage;
+    }
+    if (parameterUsage.isBottom()) {
+      return this;
+    }
+    if (isTop() || parameterUsage.isTop()) {
+      return top();
+    }
+    return asInternalNonEmpty().join(parameterUsage.asInternalNonEmpty());
+  }
+
+  abstract ParameterUsage setParameterMutated();
+
+  abstract ParameterUsage setParameterReturned();
+
+  abstract ParameterUsage setParameterUsedAsLock();
+
+  public static BottomParameterUsage bottom() {
+    return BottomParameterUsage.getInstance();
+  }
+
+  public static UnknownParameterUsage top() {
+    return UnknownParameterUsage.getInstance();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java
new file mode 100644
index 0000000..f294ea2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsagePerContext.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import java.util.function.BiFunction;
+
+public abstract class ParameterUsagePerContext {
+
+  NonEmptyParameterUsagePerContext asKnown() {
+    return null;
+  }
+
+  abstract ParameterUsagePerContext externalize();
+
+  /** Returns the usage information for this parameter in the given context. */
+  public abstract ParameterUsage get(AnalysisContext context);
+
+  /**
+   * Returns true if this is an instance of {@link BottomParameterUsagePerContext}.
+   *
+   * <p>In this case, the given parameter is always eligible for class inlining.
+   */
+  public boolean isBottom() {
+    return false;
+  }
+
+  /**
+   * Returns true if this is an instance of {@link UnknownParameterUsagePerContext}.
+   *
+   * <p>In this case, the given parameter is never eligible for class inlining.
+   */
+  public boolean isTop() {
+    return false;
+  }
+
+  ParameterUsagePerContext join(ParameterUsagePerContext parameterUsagePerContext) {
+    if (isTop() || parameterUsagePerContext.isTop()) {
+      return top();
+    }
+    return asKnown().join(parameterUsagePerContext.asKnown());
+  }
+
+  abstract ParameterUsagePerContext rebuild(
+      BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation);
+
+  static BottomParameterUsagePerContext bottom() {
+    return BottomParameterUsagePerContext.getInstance();
+  }
+
+  static UnknownParameterUsagePerContext top() {
+    return UnknownParameterUsagePerContext.getInstance();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
new file mode 100644
index 0000000..0cb3a5e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+
+public abstract class ParameterUsages extends AbstractState<ParameterUsages> {
+
+  @Override
+  public ParameterUsages asAbstractState() {
+    return this;
+  }
+
+  public NonEmptyParameterUsages asNonEmpty() {
+    return null;
+  }
+
+  /**
+   * Prepares this instance for being stored in the optimization info. This converts instances
+   * inside this {@link ParameterUsages} instance that are not suitable for being stored in
+   * optimization info into instances that can be stored in the optimization info.
+   *
+   * <p>For example, converts instances of {@link InternalNonEmptyParameterUsage} to {@link
+   * NonEmptyParameterUsage}. This is needed because {@link InternalNonEmptyParameterUsage} is not
+   * suitable for being stored in {@link
+   * com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}, since it contains references to
+   * IR instructions.
+   */
+  abstract ParameterUsages externalize();
+
+  public abstract ParameterUsagePerContext get(int parameter);
+
+  public boolean isBottom() {
+    return false;
+  }
+
+  public boolean isTop() {
+    return false;
+  }
+
+  @Override
+  public ParameterUsages join(ParameterUsages state) {
+    if (isBottom()) {
+      return state;
+    }
+    if (state.isBottom()) {
+      return this;
+    }
+    if (isTop() || state.isTop()) {
+      return top();
+    }
+    return asNonEmpty().join(state.asNonEmpty());
+  }
+
+  abstract ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext);
+
+  public static BottomParameterUsages bottom() {
+    return BottomParameterUsages.getInstance();
+  }
+
+  public static UnknownParameterUsages top() {
+    return UnknownParameterUsages.getInstance();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
new file mode 100644
index 0000000..8cbade6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -0,0 +1,386 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.MONITOR;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.AliasedValueConfiguration;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeInterface;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+class TransferFunction implements AbstractTransferFunction<ParameterUsages> {
+
+  private static final AliasedValueConfiguration aliasedValueConfiguration =
+      AssumeAndCheckCastAliasedValueConfiguration.getInstance();
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory dexItemFactory;
+  private final ProgramMethod method;
+
+  // The last argument instruction.
+  private final Argument lastArgument;
+
+  // Caches the parent or forwarding constructor call (only used in constructors).
+  private InvokeDirect constructorInvoke;
+
+  // The arguments that are considered by the analysis. We don't consider primitive arguments since
+  // they cannot be class inlined.
+  private Set<Value> argumentsOfInterest = Sets.newIdentityHashSet();
+
+  // Instructions that use one of the arguments. Instructions that don't use any of the arguments
+  // do not have any impact on the ability to class inline the arguments, therefore they are
+  // skipped.
+  private Set<Instruction> instructionsOfInterest = Sets.newIdentityHashSet();
+
+  TransferFunction(AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+    this.method = method;
+    this.lastArgument = code.getLastArgument();
+  }
+
+  @Override
+  public TransferFunctionResult<ParameterUsages> apply(
+      Instruction instruction, ParameterUsages state) {
+    if (instruction.isArgument()) {
+      Argument argument = instruction.asArgument();
+      ParameterUsages result = analyzeArgument(argument, state);
+      // After analyzing the last argument instruction, only proceed if there is at least one
+      // argument that may be eligible for class inlining.
+      if (argument == lastArgument
+          && result.asNonEmpty().allMatch((context, usagePerContext) -> usagePerContext.isTop())) {
+        return new FailedTransferFunctionResult<>();
+      }
+      return result;
+    }
+    if (!instructionsOfInterest.contains(instruction)) {
+      // The instruction does not use any of the argument values that we are analyzing, so for the
+      // purpose of class inlining we can ignore this instruction.
+      return state;
+    }
+    assert !state.isBottom();
+    assert !state.isTop();
+    return apply(instruction, state.asNonEmpty());
+  }
+
+  private ParameterUsages apply(Instruction instruction, NonEmptyParameterUsages state) {
+    switch (instruction.opcode()) {
+      case ASSUME:
+        return analyzeAssume(instruction.asAssume(), state);
+      case CHECK_CAST:
+        return analyzeCheckCast(instruction.asCheckCast(), state);
+      case IF:
+        return analyzeIf(instruction.asIf(), state);
+      case INSTANCE_GET:
+        return analyzeInstanceGet(instruction.asInstanceGet(), state);
+      case INSTANCE_PUT:
+        return analyzeInstancePut(instruction.asInstancePut(), state);
+      case INVOKE_DIRECT:
+        return analyzeInvokeDirect(instruction.asInvokeDirect(), state);
+      case INVOKE_INTERFACE:
+        return analyzeInvokeInterface(instruction.asInvokeInterface(), state);
+      case INVOKE_STATIC:
+        return analyzeInvokeStatic(instruction.asInvokeStatic(), state);
+      case INVOKE_VIRTUAL:
+        return analyzeInvokeVirtual(instruction.asInvokeVirtual(), state);
+      case MONITOR:
+        return analyzeMonitor(instruction.asMonitor(), state);
+      case RETURN:
+        return analyzeReturn(instruction.asReturn(), state);
+      default:
+        return fail(instruction, state);
+    }
+  }
+
+  @Override
+  public ParameterUsages computeBlockEntryState(
+      BasicBlock block, BasicBlock predecessor, ParameterUsages predecessorExitState) {
+    // TODO(b/173337498): Fork a new `FIELD=x` analysis context for the successor block if the
+    //  predecessor ends with an if or switch instruction, and the successor block is the
+    //  `FIELD=x` target of the predecessor. To avoid an excessive number of contexts being
+    //  created, only allow forking new contexts for $r8$classId fields synthesized by the
+    //  horizontal class merger.
+    return predecessorExitState;
+  }
+
+  private ParameterUsages analyzeArgument(Argument argument, ParameterUsages state) {
+    // Only consider arguments that could store an instance eligible for class inlining. Note that
+    // we can't ignore parameters with a library type, since instances of program classes could
+    // still flow into such parameters.
+    Value value = argument.outValue();
+    if (!isMaybeEligibleForClassInlining(value.getType()) || value.hasPhiUsers()) {
+      return state.put(argument.getIndex(), ParameterUsagePerContext.top());
+    }
+
+    // Mark the users of this argument for analysis, and fork the analysis of this argument in the
+    // default analysis context.
+    argumentsOfInterest.add(value);
+    instructionsOfInterest.addAll(value.aliasedUsers(aliasedValueConfiguration));
+    return state.put(argument.getIndex(), NonEmptyParameterUsagePerContext.createInitial());
+  }
+
+  private ParameterUsages analyzeAssume(Assume assume, NonEmptyParameterUsages state) {
+    // Mark the value as ineligible for class inlining if it has phi users.
+    return assume.outValue().hasPhiUsers() ? fail(assume, state) : state;
+  }
+
+  private ParameterUsages analyzeCheckCast(CheckCast checkCast, NonEmptyParameterUsages state) {
+    // Mark the value as ineligible for class inlining if it has phi users.
+    return checkCast.outValue().hasPhiUsers() ? fail(checkCast, state) : state;
+  }
+
+  private ParameterUsages analyzeIf(If theIf, NonEmptyParameterUsages state) {
+    // Null/not-null tests are ok.
+    if (theIf.isZeroTest()) {
+      assert argumentsOfInterest.contains(theIf.lhs().getAliasedValue(aliasedValueConfiguration));
+      return state;
+    }
+
+    // For non-null tests, mark the inputs as ineligible for class inlining.
+    return fail(theIf, state);
+  }
+
+  private ParameterUsages analyzeInstanceGet(
+      InstanceGet instanceGet, NonEmptyParameterUsages state) {
+    // Instance field reads are OK, as long as the field resolves, since the class inliner will
+    // just replace the field read by the value of the field.
+    FieldResolutionResult resolutionResult = appView.appInfo().resolveField(instanceGet.getField());
+    if (resolutionResult.isSuccessfulResolution()) {
+      // Record that the field is read from the parameter. For class inlining of singletons, this
+      // parameter is only eligible for class inlining if the singleton's field value is known.
+      return state.rebuildParameter(
+          instanceGet.object(),
+          (context, usage) -> usage.addFieldReadFromParameter(instanceGet.getField()));
+    }
+
+    return fail(instanceGet, state);
+  }
+
+  private ParameterUsages analyzeInstancePut(
+      InstancePut instancePut, NonEmptyParameterUsages state) {
+    // Instance field writes are OK, as long as the field resolves and the receiver is not being
+    // assigned (in that case the receiver escapes, and thus it is not eligible for class
+    // inlining).
+    Value valueRoot = instancePut.value().getAliasedValue(aliasedValueConfiguration);
+    if (isArgumentOfInterest(valueRoot)) {
+      state = state.abandonClassInliningInCurrentContexts(valueRoot);
+    }
+
+    Value objectRoot = instancePut.object().getAliasedValue(aliasedValueConfiguration);
+    if (!isArgumentOfInterest(objectRoot)) {
+      return state;
+    }
+
+    FieldResolutionResult resolutionResult = appView.appInfo().resolveField(instancePut.getField());
+    if (resolutionResult.isSuccessfulResolution()) {
+      return state.rebuildParameter(objectRoot, (context, usage) -> usage.setParameterMutated());
+    } else {
+      return state.abandonClassInliningInCurrentContexts(objectRoot);
+    }
+  }
+
+  private ParameterUsages analyzeInvokeDirect(InvokeDirect invoke, NonEmptyParameterUsages state) {
+    // We generally don't class inline instances that escape through invoke-direct calls, but we
+    // make an exception for forwarding/parent constructor calls that does not leak the receiver.
+    state =
+        state.abandonClassInliningInCurrentContexts(
+            invoke.getNonReceiverArguments(), this::isArgumentOfInterest);
+
+    Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration);
+    if (!isArgumentOfInterest(receiverRoot)) {
+      return state;
+    }
+
+    if (!receiverRoot.isThis()
+        || !method.getDefinition().isInstanceInitializer()
+        || !invoke.isInvokeConstructor(dexItemFactory)) {
+      return state.abandonClassInliningInCurrentContexts(receiverRoot);
+    }
+
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution();
+    if (resolutionResult == null) {
+      return state.abandonClassInliningInCurrentContexts(receiverRoot);
+    }
+
+    InstanceInitializerInfo instanceInitializerInfo =
+        resolutionResult
+            .getResolvedMethod()
+            .getOptimizationInfo()
+            .getInstanceInitializerInfo(invoke);
+    if (instanceInitializerInfo.receiverMayEscapeOutsideConstructorChain()) {
+      return state.abandonClassInliningInCurrentContexts(receiverRoot);
+    }
+
+    // We require that there is exactly one forwarding/parent constructor call.
+    if (constructorInvoke != null && constructorInvoke != invoke) {
+      return state.abandonClassInliningInCurrentContexts(receiverRoot);
+    }
+
+    constructorInvoke = invoke;
+    return state;
+  }
+
+  private ParameterUsages analyzeInvokeInterface(
+      InvokeInterface invoke, NonEmptyParameterUsages state) {
+    // We only allow invoke-interface instructions where the parameter is in the receiver position.
+    state =
+        state.abandonClassInliningInCurrentContexts(
+            invoke.getNonReceiverArguments(), this::isArgumentOfInterest);
+
+    Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration);
+    if (!isArgumentOfInterest(receiverRoot)) {
+      return state;
+    }
+
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnInterface(invoke.getInvokedMethod()).asSingleResolution();
+    if (resolutionResult == null) {
+      return state.abandonClassInliningInCurrentContexts(receiverRoot);
+    }
+
+    return state.rebuildParameter(
+        receiverRoot, (context, usage) -> usage.addMethodCallWithParameterAsReceiver(invoke));
+  }
+
+  private ParameterUsages analyzeInvokeStatic(InvokeStatic invoke, NonEmptyParameterUsages state) {
+    // We generally don't class inline instances that escape through invoke-static calls, but we
+    // make an exception for calls to Objects.requireNonNull().
+    SingleResolutionResult resolutionResult =
+        appView
+            .appInfo()
+            .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+            .asSingleResolution();
+    if (resolutionResult != null
+        && resolutionResult.getResolvedMethod().getReference()
+            == dexItemFactory.objectsMethods.requireNonNull) {
+      return state;
+    }
+
+    return fail(invoke, state);
+  }
+
+  private ParameterUsages analyzeInvokeVirtual(
+      InvokeVirtual invoke, NonEmptyParameterUsages state) {
+    // We only allow invoke-virtual instructions where the parameter is in the receiver position.
+    state =
+        state.abandonClassInliningInCurrentContexts(
+            invoke.getNonReceiverArguments(), this::isArgumentOfInterest);
+
+    Value receiverRoot = invoke.getReceiver().getAliasedValue(aliasedValueConfiguration);
+    if (!isArgumentOfInterest(receiverRoot)) {
+      return state;
+    }
+
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution();
+    if (resolutionResult == null) {
+      return state.abandonClassInliningInCurrentContexts(receiverRoot);
+    }
+
+    return state.rebuildParameter(
+        receiverRoot, (context, usage) -> usage.addMethodCallWithParameterAsReceiver(invoke));
+  }
+
+  private ParameterUsages analyzeMonitor(Monitor monitor, NonEmptyParameterUsages state) {
+    // Record that the receiver is used as a lock in each context that may reach this monitor
+    // instruction.
+    return state.rebuildParameter(
+        monitor.object(), (context, usage) -> usage.setParameterUsedAsLock());
+  }
+
+  private ParameterUsages analyzeReturn(Return theReturn, NonEmptyParameterUsages state) {
+    return state.rebuildParameter(
+        theReturn.returnValue(), (context, usage) -> usage.setParameterReturned());
+  }
+
+  private ParameterUsages fail(Instruction instruction, NonEmptyParameterUsages state) {
+    return state.abandonClassInliningInCurrentContexts(
+        instruction.inValues(), this::isArgumentOfInterest);
+  }
+
+  private boolean isArgumentOfInterest(Value value) {
+    assert value.getAliasedValue(aliasedValueConfiguration) == value;
+    return value.isArgument() && argumentsOfInterest.contains(value);
+  }
+
+  private boolean isMaybeEligibleForClassInlining(TypeElement type) {
+    if (!type.isClassType()) {
+      // Primitives and arrays will never be class inlined.
+      return false;
+    }
+    DexClass clazz = appView.definitionFor(type.asClassType().getClassType());
+    if (clazz == null) {
+      // We cannot class inline in presence of missing classes.
+      return false;
+    }
+    return clazz.isProgramClass()
+        ? isMaybeEligibleForClassInlining(clazz.asProgramClass())
+        : isMaybeEligibleForClassInlining(clazz.asClasspathOrLibraryClass());
+  }
+
+  private boolean isMaybeEligibleForClassInlining(DexProgramClass clazz) {
+    // We can only class inline parameters that does not inherit from other classpath or library
+    // classes than java.lang.Object.
+    DexType superType = clazz.getSuperType();
+    do {
+      DexClass superClass = appView.definitionFor(superType);
+      if (superClass == null) {
+        return false;
+      }
+      if (!superClass.isProgramClass()) {
+        return superClass.getType() == dexItemFactory.objectType;
+      }
+      superType = superClass.getSuperType();
+    } while (true);
+  }
+
+  private boolean isMaybeEligibleForClassInlining(ClasspathOrLibraryClass clazz) {
+    // We can only class inline a parameter that is either java.lang.Object or an interface type.
+    return clazz.getType() == dexItemFactory.objectType || clazz.isInterface();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
new file mode 100644
index 0000000..93577ad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsage.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+
+class UnknownParameterUsage extends ParameterUsage {
+
+  private static final UnknownParameterUsage TOP = new UnknownParameterUsage();
+
+  private UnknownParameterUsage() {}
+
+  public static UnknownParameterUsage getInstance() {
+    return TOP;
+  }
+
+  @Override
+  UnknownParameterUsage addFieldReadFromParameter(DexField field) {
+    return this;
+  }
+
+  @Override
+  UnknownParameterUsage addMethodCallWithParameterAsReceiver(InvokeMethodWithReceiver invoke) {
+    return this;
+  }
+
+  @Override
+  ParameterUsage externalize() {
+    return this;
+  }
+
+  @Override
+  public boolean isParameterMutated() {
+    return true;
+  }
+
+  @Override
+  public boolean isParameterReturned() {
+    return true;
+  }
+
+  @Override
+  public boolean isParameterUsedAsLock() {
+    return true;
+  }
+
+  @Override
+  public boolean isTop() {
+    return true;
+  }
+
+  @Override
+  UnknownParameterUsage setParameterMutated() {
+    return this;
+  }
+
+  @Override
+  UnknownParameterUsage setParameterReturned() {
+    return this;
+  }
+
+  @Override
+  UnknownParameterUsage setParameterUsedAsLock() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java
new file mode 100644
index 0000000..4ead51d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsagePerContext.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+import java.util.function.BiFunction;
+
+class UnknownParameterUsagePerContext extends ParameterUsagePerContext {
+
+  private static final UnknownParameterUsagePerContext INSTANCE =
+      new UnknownParameterUsagePerContext();
+
+  private UnknownParameterUsagePerContext() {}
+
+  static UnknownParameterUsagePerContext getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  ParameterUsagePerContext externalize() {
+    return this;
+  }
+
+  @Override
+  public ParameterUsage get(AnalysisContext context) {
+    return ParameterUsage.top();
+  }
+
+  @Override
+  public boolean isTop() {
+    return true;
+  }
+
+  @Override
+  ParameterUsagePerContext rebuild(
+      BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
new file mode 100644
index 0000000..13e32a7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.analysis;
+
+public class UnknownParameterUsages extends ParameterUsages {
+
+  private static final UnknownParameterUsages INSTANCE = new UnknownParameterUsages();
+
+  private UnknownParameterUsages() {}
+
+  static UnknownParameterUsages getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  ParameterUsages externalize() {
+    return this;
+  }
+
+  @Override
+  public ParameterUsagePerContext get(int parameter) {
+    return ParameterUsagePerContext.top();
+  }
+
+  @Override
+  public boolean isTop() {
+    return true;
+  }
+
+  @Override
+  ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext) {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return other == INSTANCE;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
index c441134..6ddae8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class AlwaysFalseClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
 
@@ -13,17 +17,31 @@
 
   private AlwaysFalseClassInlinerMethodConstraint() {}
 
-  public static AlwaysFalseClassInlinerMethodConstraint getInstance() {
+  static AlwaysFalseClassInlinerMethodConstraint getInstance() {
     return INSTANCE;
   }
 
   @Override
-  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) {
+  public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() {
+    return this;
+  }
+
+  @Override
+  public ParameterUsage getParameterUsage(int parameter) {
+    return ParameterUsage.top();
+  }
+
+  @Override
+  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
     return false;
   }
 
   @Override
-  public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) {
+  public boolean isEligibleForStaticGetClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      int parameter,
+      ObjectState objectState,
+      ProgramMethod context) {
     return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
index 3488111..dc89cf4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysTrueClassInlinerMethodConstraint.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class AlwaysTrueClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
 
@@ -13,17 +17,31 @@
 
   private AlwaysTrueClassInlinerMethodConstraint() {}
 
-  public static AlwaysTrueClassInlinerMethodConstraint getInstance() {
+  static AlwaysTrueClassInlinerMethodConstraint getInstance() {
     return INSTANCE;
   }
 
   @Override
-  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) {
+  public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() {
+    return this;
+  }
+
+  @Override
+  public ParameterUsage getParameterUsage(int parameter) {
+    return ParameterUsage.bottom();
+  }
+
+  @Override
+  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
     return true;
   }
 
   @Override
-  public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) {
+  public boolean isEligibleForStaticGetClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      int parameter,
+      ObjectState objectState,
+      ProgramMethod context) {
     return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index e8ab3ab..22079fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -4,11 +4,31 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public interface ClassInlinerMethodConstraint {
 
-  boolean isEligibleForNewInstanceClassInlining(ProgramMethod method);
+  ClassInlinerMethodConstraint fixupAfterRemovingThisParameter();
 
-  boolean isEligibleForStaticGetClassInlining(ProgramMethod method);
+  ParameterUsage getParameterUsage(int parameter);
+
+  boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter);
+
+  boolean isEligibleForStaticGetClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      int parameter,
+      ObjectState objectState,
+      ProgramMethod context);
+
+  static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() {
+    return AlwaysFalseClassInlinerMethodConstraint.getInstance();
+  }
+
+  static AlwaysTrueClassInlinerMethodConstraint alwaysTrue() {
+    return AlwaysTrueClassInlinerMethodConstraint.getInstance();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java
deleted file mode 100644
index fea67a4..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraintAnalysis.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.classinliner.constraint;
-
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-
-public class ClassInlinerMethodConstraintAnalysis {
-
-  public static ClassInlinerMethodConstraint analyze(
-      ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
-      ParameterUsagesInfo parameterUsagesInfo) {
-    boolean isEligibleForNewInstanceClassInlining =
-        isEligibleForNewInstanceClassInlining(classInlinerEligibilityInfo);
-    boolean isEligibleForStaticGetClassInlining =
-        isEligibleForStaticGetClassInlining(classInlinerEligibilityInfo, parameterUsagesInfo);
-    if (isEligibleForNewInstanceClassInlining) {
-      if (isEligibleForStaticGetClassInlining) {
-        return alwaysTrue();
-      }
-      return onlyNewInstanceClassInlining();
-    }
-    assert !isEligibleForStaticGetClassInlining;
-    return alwaysFalse();
-  }
-
-  private static boolean isEligibleForNewInstanceClassInlining(
-      ClassInlinerEligibilityInfo classInlinerEligibilityInfo) {
-    return classInlinerEligibilityInfo != null;
-  }
-
-  private static boolean isEligibleForStaticGetClassInlining(
-      ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
-      ParameterUsagesInfo parameterUsagesInfo) {
-    if (classInlinerEligibilityInfo == null || parameterUsagesInfo == null) {
-      return false;
-    }
-    if (classInlinerEligibilityInfo.hasMonitorOnReceiver) {
-      // We will not be able to remove the monitor instruction afterwards.
-      return false;
-    }
-    if (classInlinerEligibilityInfo.modifiesInstanceFields) {
-      // The static instance could be accessed from elsewhere. Therefore, we cannot allow
-      // side-effects to be removed and therefore cannot class inline method calls that modifies the
-      // instance.
-      return false;
-    }
-    ParameterUsage receiverUsage = parameterUsagesInfo.getParameterUsage(0);
-    if (receiverUsage == null) {
-      return false;
-    }
-    if (receiverUsage.hasFieldRead) {
-      // We don't know the value of the field.
-      return false;
-    }
-    return true;
-  }
-
-  private static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() {
-    return AlwaysFalseClassInlinerMethodConstraint.getInstance();
-  }
-
-  private static AlwaysTrueClassInlinerMethodConstraint alwaysTrue() {
-    return AlwaysTrueClassInlinerMethodConstraint.getInstance();
-  }
-
-  private static OnlyNewInstanceClassInlinerMethodConstraint onlyNewInstanceClassInlining() {
-    return OnlyNewInstanceClassInlinerMethodConstraint.getInstance();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
new file mode 100644
index 0000000..eb743cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.constraint;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsages;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsagePerContext;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsages;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+public class ConditionalClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
+
+  private final ParameterUsages usages;
+
+  public ConditionalClassInlinerMethodConstraint(ParameterUsages usages) {
+    assert !usages.isTop();
+    this.usages = usages;
+  }
+
+  @Override
+  public ClassInlinerMethodConstraint fixupAfterRemovingThisParameter() {
+    if (usages.isBottom()) {
+      return this;
+    }
+    Int2ObjectMap<ParameterUsagePerContext> backing = new Int2ObjectOpenHashMap<>();
+    usages
+        .asNonEmpty()
+        .forEach(
+            (parameter, usagePerContext) -> {
+              if (parameter > 0) {
+                backing.put(parameter - 1, usagePerContext);
+              }
+            });
+    return new ConditionalClassInlinerMethodConstraint(NonEmptyParameterUsages.create(backing));
+  }
+
+  @Override
+  public ParameterUsage getParameterUsage(int parameter) {
+    AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
+    return usages.get(parameter).get(defaultContext);
+  }
+
+  @Override
+  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter) {
+    AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
+    ParameterUsage usage = usages.get(parameter).get(defaultContext);
+    return !usage.isTop();
+  }
+
+  @Override
+  public boolean isEligibleForStaticGetClassInlining(
+      AppView<AppInfoWithLiveness> appView,
+      int parameter,
+      ObjectState objectState,
+      ProgramMethod context) {
+    AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
+    ParameterUsage usage = usages.get(parameter).get(defaultContext);
+    if (usage.isBottom()) {
+      return true;
+    }
+    if (usage.isTop()) {
+      return false;
+    }
+
+    NonEmptyParameterUsage knownUsage = usage.asNonEmpty();
+    if (knownUsage.isParameterMutated()) {
+      // The static instance could be accessed from elsewhere. Therefore, we cannot allow
+      // side-effects to be removed and therefore cannot class inline method calls that modifies the
+      // instance.
+      return false;
+    }
+    if (knownUsage.isParameterUsedAsLock()) {
+      // We will not be able to remove the monitor instruction afterwards.
+      return false;
+    }
+    for (DexField fieldReadFromParameter : knownUsage.getFieldsReadFromParameter()) {
+      DexClass holder = appView.definitionFor(fieldReadFromParameter.getHolderType());
+      DexEncodedField definition = fieldReadFromParameter.lookupOnClass(holder);
+      if (definition == null) {
+        return false;
+      }
+      AbstractValue abstractValue = objectState.getAbstractFieldValue(definition);
+      if (!abstractValue.isSingleConstValue()) {
+        return false;
+      }
+      SingleConstValue singleConstValue = abstractValue.asSingleConstValue();
+      if (!singleConstValue.isMaterializableInContext(appView, context)) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java
deleted file mode 100644
index d3f35d4..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/OnlyNewInstanceClassInlinerMethodConstraint.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.classinliner.constraint;
-
-import com.android.tools.r8.graph.ProgramMethod;
-
-public class OnlyNewInstanceClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
-
-  private static final OnlyNewInstanceClassInlinerMethodConstraint INSTANCE =
-      new OnlyNewInstanceClassInlinerMethodConstraint();
-
-  private OnlyNewInstanceClassInlinerMethodConstraint() {}
-
-  public static OnlyNewInstanceClassInlinerMethodConstraint getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public boolean isEligibleForNewInstanceClassInlining(ProgramMethod method) {
-    return true;
-  }
-
-  @Override
-  public boolean isEligibleForStaticGetClassInlining(ProgramMethod method) {
-    return false;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
index 76f63ac..4e01bc5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/controlflow/SwitchCaseAnalyzer.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.ir.optimize.controlflow;
 
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.utils.LongInterval;
@@ -37,11 +39,17 @@
         && key == rootSwitchValue.definition.asConstString().getValue();
   }
 
-  public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
+  public boolean switchCaseIsUnreachable(
+      Switch theSwitch, AbstractValue switchAbstractValue, int index) {
     Value switchValue = theSwitch.value();
     if (theSwitch.isIntSwitch()) {
-      return switchValue.hasValueRange()
-          && !switchValue.getValueRange().containsValue(theSwitch.asIntSwitch().getKey(index));
+      int key = theSwitch.asIntSwitch().getKey(index);
+      if (switchAbstractValue.isConstantOrNonConstantNumberValue()
+          && !switchAbstractValue.asConstantOrNonConstantNumberValue().containsInt(key)) {
+        return true;
+      }
+      // TODO(b/150836439): Reimplement using AbstractValue.
+      return switchValue.hasValueRange() && !switchValue.getValueRange().containsValue(key);
     }
 
     assert theSwitch.isStringSwitch();
@@ -51,4 +59,21 @@
     return rootSwitchValue.isDefinedByInstructionSatisfying(Instruction::isConstString)
         && key != rootSwitchValue.definition.asConstString().getValue();
   }
+
+  public boolean switchFallthroughIsNeverHit(Switch theSwitch, AbstractValue switchAbstractValue) {
+    if (theSwitch.isIntSwitch()) {
+      IntSwitch intSwitch = theSwitch.asIntSwitch();
+      if (switchAbstractValue.isConstantOrNonConstantNumberValue()) {
+        return switchAbstractValue
+            .asConstantOrNonConstantNumberValue()
+            .isSubsetOf(intSwitch.getKeys())
+            .isTrue();
+      }
+      return false;
+    }
+
+    assert theSwitch.isStringSwitch();
+
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
index 48e545c..c29dd86 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
-import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
@@ -24,7 +23,6 @@
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
@@ -34,7 +32,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Predicate;
 
 public class UnboxedEnumMemberRelocator {
 
@@ -132,7 +129,7 @@
         Set<DexProgramClass> relocatedEnums,
         DirectMappedDexApplication.Builder appBuilder,
         FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      DexProgramClass deterministicContext = findDeterministicContextType(contexts, alwaysTrue());
+      DexProgramClass deterministicContext = findDeterministicContextType(contexts);
       String descriptorString = deterministicContext.getType().toDescriptorString();
       String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
       String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
@@ -182,21 +179,13 @@
               appView.dexItemFactory().getSkipNameValidationForTesting(),
               DexProgramClass::checksumFromType);
       appBuilder.addSynthesizedClass(syntheticClass);
-      MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
-      appView
-          .appInfo()
-          .addSynthesizedClass(
-              syntheticClass, findDeterministicContextType(contexts, mainDexInfo::isMainDex));
+      appView.appInfo().addSynthesizedClass(syntheticClass, contexts);
       return syntheticClass;
     }
 
-    private DexProgramClass findDeterministicContextType(
-        Set<DexProgramClass> contexts, Predicate<DexProgramClass> predicate) {
+    private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
       DexProgramClass deterministicContext = null;
       for (DexProgramClass context : contexts) {
-        if (!predicate.test(context)) {
-          continue;
-        }
         if (deterministicContext == null) {
           deterministicContext = context;
         } else if (context.type.compareTo(deterministicContext.type) < 0) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index ded60a3..ab2b0ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -40,16 +39,21 @@
 
   CallSiteOptimizationInfo join(
       ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) {
-    assert this.size == other.size;
+    assert size == other.size;
+    assert size == method.getNumberOfArguments();
     boolean allowConstantPropagation =
         appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
     ConcreteCallSiteOptimizationInfo result =
-        new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation);
+        new ConcreteCallSiteOptimizationInfo(size, allowConstantPropagation);
     for (int i = 0; i < result.size; i++) {
       if (allowConstantPropagation) {
         assert result.constants != null;
         AbstractValue abstractValue =
-            getAbstractArgumentValue(i).join(other.getAbstractArgumentValue(i));
+            getAbstractArgumentValue(i)
+                .join(
+                    other.getAbstractArgumentValue(i),
+                    appView.abstractValueFactory(),
+                    method.getArgumentType(i));
         if (abstractValue.isNonTrivial()) {
           result.constants.put(i, abstractValue);
         }
@@ -94,11 +98,6 @@
   public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) {
     TypeElement[] staticTypes = getStaticTypes(appView, method);
     for (int i = 0; i < size; i++) {
-      ParameterUsage parameterUsage = method.getOptimizationInfo().getParameterUsages(i);
-      // If the parameter is not used, passing accurate argument info doesn't matter.
-      if (parameterUsage != null && parameterUsage.notUsed()) {
-        continue;
-      }
       AbstractValue abstractValue = getAbstractArgumentValue(i);
       if (abstractValue.isNonTrivial()) {
         assert appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 45764df..d49189f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -12,10 +12,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.classinliner.constraint.AlwaysFalseClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -36,9 +33,7 @@
   static ClassTypeElement UNKNOWN_CLASS_TYPE = null;
   static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
   static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
-  static ClassInlinerEligibilityInfo UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
   static boolean UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS = false;
-  static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null;
   static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
   static boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false;
   static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
@@ -77,7 +72,7 @@
 
   @Override
   public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() {
-    return AlwaysFalseClassInlinerMethodConstraint.getInstance();
+    return ClassInlinerMethodConstraint.alwaysFalse();
   }
 
   @Override
@@ -106,12 +101,6 @@
   }
 
   @Override
-  public ParameterUsage getParameterUsages(int parameter) {
-    assert UNKNOWN_PARAMETER_USAGE_INFO == null;
-    return null;
-  }
-
-  @Override
   public BitSet getNonNullParamOrThrow() {
     return NO_NULL_PARAMETER_OR_THROW_FACTS;
   }
@@ -153,11 +142,6 @@
   }
 
   @Override
-  public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
-    return UNKNOWN_CLASS_INLINER_ELIGIBILITY;
-  }
-
-  @Override
   public AbstractValue getAbstractReturnValue() {
     return UNKNOWN_ABSTRACT_RETURN_VALUE;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 7cced9d..bdc35f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -10,9 +10,7 @@
 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.InvokeDirect;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import java.util.BitSet;
@@ -47,8 +45,6 @@
 
   public abstract ClassTypeElement getDynamicLowerBoundType();
 
-  public abstract ParameterUsage getParameterUsages(int parameter);
-
   public final boolean hasNonNullParamOrThrow() {
     return getNonNullParamOrThrow() != null;
   }
@@ -73,8 +69,6 @@
 
   public abstract BridgeInfo getBridgeInfo();
 
-  public abstract ClassInlinerEligibilityInfo getClassInlinerEligibility();
-
   public abstract Set<DexType> getInitializedClassesOnNormalExit();
 
   public abstract InstanceInitializerInfo getContextInsensitiveInstanceInitializerInfo();
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 804b6b3..aa28923 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
@@ -29,7 +29,6 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
-import static com.android.tools.r8.ir.code.Opcodes.MONITOR;
 import static com.android.tools.r8.ir.code.Opcodes.MUL;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
@@ -47,10 +46,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -77,23 +74,17 @@
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeVirtual;
 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;
+import com.android.tools.r8.ir.optimize.classinliner.analysis.ClassInlinerMethodConstraintAnalysis;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraintAnalysis;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -103,19 +94,14 @@
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
-import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Deque;
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiFunction;
-import java.util.function.Predicate;
 
 public class MethodOptimizationInfoCollector {
 
@@ -145,16 +131,11 @@
       Timing timing) {
     DexEncodedMethod definition = method.getDefinition();
     identifyBridgeInfo(definition, code, feedback, timing);
-    ClassInlinerEligibilityInfo classInlinerEligibilityInfo =
-        identifyClassInlinerEligibility(code, feedback, timing);
-    ParameterUsagesInfo parameterUsagesInfo =
-        identifyParameterUsages(definition, code, feedback, timing);
     analyzeReturns(code, feedback, timing);
     if (options.enableInlining) {
       identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
-    computeClassInlinerMethodConstraint(
-        method, code, feedback, classInlinerEligibilityInfo, parameterUsagesInfo, timing);
+    computeClassInlinerMethodConstraint(method, code, feedback, timing);
     computeSimpleInliningConstraint(method, code, feedback, timing);
     computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
     computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
@@ -173,201 +154,6 @@
     timing.end();
   }
 
-  private ClassInlinerEligibilityInfo identifyClassInlinerEligibility(
-      IRCode code, OptimizationFeedback feedback, Timing timing) {
-    timing.begin("Identify class inliner eligibility");
-    ClassInlinerEligibilityInfo classInlinerEligibilityInfo =
-        identifyClassInlinerEligibility(code, feedback);
-    timing.end();
-    return classInlinerEligibilityInfo;
-  }
-
-  private ClassInlinerEligibilityInfo identifyClassInlinerEligibility(
-      IRCode code, OptimizationFeedback feedback) {
-    // Method eligibility is calculated in similar way for regular method
-    // and for the constructor. To be eligible method should only be using its
-    // receiver in the following ways:
-    //
-    //  (1) as a receiver of reads/writes of instance fields of the holder class,
-    //  (2) as a return value,
-    //  (3) as a receiver of a call to the superclass initializer. Note that we don't
-    //      check what is passed to superclass initializer as arguments, only check
-    //      that it is not the instance being initialized,
-    //  (4) as argument to a monitor instruction.
-    //
-    // Note that (4) can safely be removed as the receiver is guaranteed not to escape when we class
-    // inline it, and hence any monitor instructions are no-ops.
-    ProgramMethod context = code.context();
-    DexEncodedMethod definition = context.getDefinition();
-    boolean instanceInitializer = definition.isInstanceInitializer();
-    if (definition.isNative()
-        || (!definition.isNonAbstractVirtualMethod() && !instanceInitializer)) {
-      return null;
-    }
-
-    feedback.setClassInlinerEligibility(definition, null); // To allow returns below.
-
-    Value receiver = code.getThis();
-    if (receiver.numberOfPhiUsers() > 0) {
-      return null;
-    }
-
-    List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>();
-    boolean seenSuperInitCall = false;
-    boolean seenMonitor = false;
-    boolean modifiesInstanceFields = false;
-
-    AliasedValueConfiguration configuration =
-        AssumeAndCheckCastAliasedValueConfiguration.getInstance();
-    Predicate<Value> isReceiverAlias = value -> value.getAliasedValue(configuration) == receiver;
-    for (Instruction insn : receiver.aliasedUsers(configuration)) {
-      switch (insn.opcode()) {
-        case ASSUME:
-        case CHECK_CAST:
-        case RETURN:
-          break;
-
-        case MONITOR:
-          seenMonitor = true;
-          break;
-
-        case INSTANCE_GET:
-        case INSTANCE_PUT:
-          {
-            if (insn.isInstancePut()) {
-              InstancePut instancePutInstruction = insn.asInstancePut();
-              // Only allow field writes to the receiver.
-              if (!isReceiverAlias.test(instancePutInstruction.object())) {
-                return null;
-              }
-              // Do not allow the receiver to escape via a field write.
-              if (isReceiverAlias.test(instancePutInstruction.value())) {
-                return null;
-              }
-              modifiesInstanceFields = true;
-            }
-            DexField field = insn.asFieldInstruction().getField();
-            if (appView.appInfo().resolveField(field).isFailedOrUnknownResolution()) {
-              return null;
-            }
-            break;
-          }
-
-        case INVOKE_DIRECT:
-          {
-            InvokeDirect invoke = insn.asInvokeDirect();
-            DexMethod invokedMethod = invoke.getInvokedMethod();
-            if (dexItemFactory.isConstructor(invokedMethod)
-                && invokedMethod.holder == context.getHolder().superType
-                && ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) == 0
-                && !seenSuperInitCall
-                && instanceInitializer) {
-              seenSuperInitCall = true;
-              break;
-            }
-            // We don't support other direct calls yet.
-            return null;
-          }
-
-        case INVOKE_STATIC:
-          {
-            InvokeStatic invoke = insn.asInvokeStatic();
-            DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
-            if (singleTarget == null) {
-              return null; // Not allowed.
-            }
-            if (singleTarget.getReference() == dexItemFactory.objectsMethods.requireNonNull) {
-              if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
-                continue;
-              }
-            }
-            return null;
-          }
-
-        case INVOKE_VIRTUAL:
-          {
-            InvokeVirtual invoke = insn.asInvokeVirtual();
-            if (ListUtils.lastIndexMatching(invoke.arguments(), isReceiverAlias) != 0) {
-              return null; // Not allowed.
-            }
-            DexMethod invokedMethod = invoke.getInvokedMethod();
-            DexType returnType = invokedMethod.proto.returnType;
-            if (returnType.isClassType()
-                && appView.appInfo().inSameHierarchy(returnType, context.getHolderType())) {
-              return null; // Not allowed, could introduce an alias of the receiver.
-            }
-            callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod));
-          }
-          break;
-
-        default:
-          // Other receiver usages make the method not eligible.
-          return null;
-      }
-    }
-
-    if (instanceInitializer && !seenSuperInitCall) {
-      // Call to super constructor not found?
-      return null;
-    }
-
-    boolean synchronizedVirtualMethod = definition.isSynchronized() && definition.isVirtualMethod();
-    ClassInlinerEligibilityInfo classInlinerEligibilityInfo =
-        new ClassInlinerEligibilityInfo(
-            callsReceiver,
-            new ClassInlinerReceiverAnalysis(appView, definition, code).computeReturnsReceiver(),
-            seenMonitor || synchronizedVirtualMethod,
-            modifiesInstanceFields);
-    feedback.setClassInlinerEligibility(definition, classInlinerEligibilityInfo);
-    return classInlinerEligibilityInfo;
-  }
-
-  private ParameterUsagesInfo identifyParameterUsages(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
-    timing.begin("Identify parameter usages");
-    ParameterUsagesInfo parameterUsagesInfo = identifyParameterUsages(method, code, feedback);
-    timing.end();
-    return parameterUsagesInfo;
-  }
-
-  private ParameterUsagesInfo identifyParameterUsages(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    List<ParameterUsage> usages = new ArrayList<>();
-    List<Value> values = code.collectArguments();
-    for (int i = 0; i < values.size(); i++) {
-      Value value = values.get(i);
-      ParameterUsage usage = collectParameterUsages(i, value);
-      if (usage != null) {
-        usages.add(usage);
-      }
-    }
-    ParameterUsagesInfo parameterUsagesInfo =
-        !usages.isEmpty() ? new ParameterUsagesInfo(usages) : null;
-    feedback.setParameterUsages(method, parameterUsagesInfo);
-    return parameterUsagesInfo;
-  }
-
-  private ParameterUsage collectParameterUsages(int i, Value root) {
-    ParameterUsageBuilder builder = new ParameterUsageBuilder(root, i, dexItemFactory);
-    WorkList<Value> worklist = WorkList.newIdentityWorkList();
-    worklist.addIfNotSeen(root);
-    while (worklist.hasNext()) {
-      Value value = worklist.next();
-      if (value.hasPhiUsers()) {
-        return null;
-      }
-      for (Instruction user : value.uniqueUsers()) {
-        if (!builder.note(user)) {
-          return null;
-        }
-        if (user.isAssume()) {
-          worklist.addIfNotSeen(user.outValue());
-        }
-      }
-    }
-    return builder.build();
-  }
-
   private void analyzeReturns(IRCode code, OptimizationFeedback feedback, Timing timing) {
     timing.begin("Identify returns argument");
     analyzeReturns(code, feedback);
@@ -982,24 +768,16 @@
       ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
-      ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
-      ParameterUsagesInfo parameterUsagesInfo,
       Timing timing) {
     timing.begin("Compute class inlining constraint");
-    computeClassInlinerMethodConstraint(
-        method, code, feedback, classInlinerEligibilityInfo, parameterUsagesInfo);
+    computeClassInlinerMethodConstraint(method, code, feedback);
     timing.end();
   }
 
   private void computeClassInlinerMethodConstraint(
-      ProgramMethod method,
-      IRCode code,
-      OptimizationFeedback feedback,
-      ClassInlinerEligibilityInfo classInlinerEligibilityInfo,
-      ParameterUsagesInfo parameterUsagesInfo) {
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
     ClassInlinerMethodConstraint classInlinerMethodConstraint =
-        ClassInlinerMethodConstraintAnalysis.analyze(
-            classInlinerEligibilityInfo, parameterUsagesInfo);
+        ClassInlinerMethodConstraintAnalysis.analyze(appView, method, code);
     feedback.setClassInlinerMethodConstraint(method, classInlinerMethodConstraint);
   }
 
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 39f725d..df06045 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
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -263,12 +262,6 @@
   }
 
   @Override
-  public synchronized void setClassInlinerEligibility(
-      DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
-    getMethodOptimizationInfoForUpdating(method).setClassInlinerEligibility(eligibility);
-  }
-
-  @Override
   public synchronized void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -282,12 +275,6 @@
   }
 
   @Override
-  public synchronized void setParameterUsages(
-      DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
-    getMethodOptimizationInfoForUpdating(method).setParameterUsages(parameterUsagesInfo);
-  }
-
-  @Override
   public synchronized void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
     getMethodOptimizationInfoForUpdating(method).setNonNullParamOrThrow(facts);
   }
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 3b46f4e..e862824 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
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -118,10 +117,6 @@
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {}
 
   @Override
-  public void setClassInlinerEligibility(
-      DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {}
-
-  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {}
@@ -130,10 +125,6 @@
   public void setInitializerEnablingJavaVmAssertions(DexEncodedMethod method) {}
 
   @Override
-  public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
-  }
-
-  @Override
   public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {}
 
   @Override
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 2624486..3fb77d9 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
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -170,12 +169,6 @@
   }
 
   @Override
-  public void setClassInlinerEligibility(
-      DexEncodedMethod method, ClassInlinerEligibilityInfo eligibility) {
-    // Ignored.
-  }
-
-  @Override
   public void setInstanceInitializerInfoCollection(
       DexEncodedMethod method,
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
@@ -190,11 +183,6 @@
   }
 
   @Override
-  public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
-    // Ignored.
-  }
-
-  @Override
   public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {
     method.getMutableOptimizationInfo().setNonNullParamOrThrow(facts);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
deleted file mode 100644
index 4389193..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.info;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.If.Type;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Monitor;
-import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.Pair;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public final class ParameterUsagesInfo {
-
-  private ImmutableList<ParameterUsage> parametersUsages;
-
-  public ParameterUsagesInfo(List<ParameterUsage> usages) {
-    assert !usages.isEmpty();
-    parametersUsages = ImmutableList.copyOf(usages);
-    assert parametersUsages.size() ==
-        parametersUsages.stream().map(usage -> usage.index).collect(Collectors.toSet()).size();
-  }
-
-  public ParameterUsage getParameterUsage(int index) {
-    for (ParameterUsage usage : parametersUsages) {
-      if (usage.index == index) {
-        return usage;
-      }
-    }
-    return null;
-  }
-
-  ParameterUsagesInfo remove(int index) {
-    assert parametersUsages.size() > 0;
-    assert 0 <= index && index <= ListUtils.last(parametersUsages).index;
-    ImmutableList.Builder<ParameterUsage> builder = ImmutableList.builder();
-    for (ParameterUsage usage : parametersUsages) {
-      // Once we remove or pass the designated index, copy-n-shift the remaining usages.
-      if (index < usage.index) {
-        builder.add(ParameterUsage.copyAndShift(usage, 1));
-      } else if (index == usage.index) {
-        // Do not add the parameter usage with the matched index.
-      } else {
-        // Until we meet the `parameter` of interest, keep copying.
-        assert usage.index < index;
-        builder.add(usage);
-      }
-    }
-    ImmutableList<ParameterUsage> adjustedUsages = builder.build();
-    if (adjustedUsages.isEmpty()) {
-      return DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
-    }
-    return new ParameterUsagesInfo(adjustedUsages);
-  }
-
-  public final static class ParameterUsage {
-
-    public final int index;
-    public final Set<Type> ifZeroTest;
-    public final List<Pair<Invoke.Type, DexMethod>> callsReceiver;
-
-    // If a field of this argument is assigned: arg.f = x.
-    public final boolean hasFieldAssignment;
-
-    // If a field of this argument is read: x = arg.f.
-    public final boolean hasFieldRead;
-
-    // If this argument is assigned to a field: x.f = arg.
-    public final boolean isAssignedToField;
-
-    // If this argument is returned: return arg.
-    public final boolean isReturned;
-
-    // If this argument is used in a monitor instruction.
-    public final boolean isUsedInMonitor;
-
-    ParameterUsage(
-        int index,
-        Set<Type> ifZeroTest,
-        List<Pair<Invoke.Type, DexMethod>> callsReceiver,
-        boolean hasFieldAssignment,
-        boolean hasFieldRead,
-        boolean isAssignedToField,
-        boolean isReturned,
-        boolean isUsedInMonitor) {
-      this.index = index;
-      this.ifZeroTest =
-          ifZeroTest.isEmpty() ? Collections.emptySet() : ImmutableSet.copyOf(ifZeroTest);
-      this.callsReceiver =
-          callsReceiver.isEmpty() ? Collections.emptyList() : ImmutableList.copyOf(callsReceiver);
-      this.hasFieldAssignment = hasFieldAssignment;
-      this.hasFieldRead = hasFieldRead;
-      this.isAssignedToField = isAssignedToField;
-      this.isReturned = isReturned;
-      this.isUsedInMonitor = isUsedInMonitor;
-    }
-
-    static ParameterUsage copyAndShift(ParameterUsage original, int shift) {
-      assert original.index >= shift;
-      return new ParameterUsage(
-          original.index - shift,
-          original.ifZeroTest,
-          original.callsReceiver,
-          original.hasFieldAssignment,
-          original.hasFieldRead,
-          original.isAssignedToField,
-          original.isReturned,
-          original.isUsedInMonitor);
-    }
-
-    public boolean notUsed() {
-      return (ifZeroTest == null || ifZeroTest.isEmpty())
-          && (callsReceiver == null || callsReceiver.isEmpty())
-          && !hasFieldAssignment
-          && !hasFieldRead
-          && !isAssignedToField
-          && !isReturned
-          && !isUsedInMonitor;
-    }
-  }
-
-  public static class ParameterUsageBuilder {
-
-    private final int index;
-    private final Value arg;
-    private final DexItemFactory dexItemFactory;
-
-    private final Set<Type> ifZeroTestTypes = new HashSet<>();
-    private final List<Pair<Invoke.Type, DexMethod>> callsOnReceiver = new ArrayList<>();
-
-    private boolean hasFieldAssignment = false;
-    private boolean hasFieldRead = false;
-    private boolean isAssignedToField = false;
-    private boolean isReturned = false;
-    private boolean isUsedInMonitor = false;
-
-    ParameterUsageBuilder(Value arg, int index, DexItemFactory dexItemFactory) {
-      this.arg = arg;
-      this.index = index;
-      this.dexItemFactory = dexItemFactory;
-    }
-
-    // Returns false if the instruction is not supported.
-    public boolean note(Instruction instruction) {
-      if (instruction.isAssume()) {
-        // Keep examining other users if there are no phi users, but the param usage builder should
-        // consider aliased users.
-        return !instruction.outValue().hasPhiUsers();
-      }
-      if (instruction.isIf()) {
-        return note(instruction.asIf());
-      }
-      if (instruction.isInstanceGet()) {
-        return note(instruction.asInstanceGet());
-      }
-      if (instruction.isInstancePut()) {
-        return note(instruction.asInstancePut());
-      }
-      if (instruction.isInvokeMethodWithReceiver()) {
-        return note(instruction.asInvokeMethodWithReceiver());
-      }
-      if (instruction.isInvokeStatic()) {
-        return note(instruction.asInvokeStatic());
-      }
-      if (instruction.isReturn()) {
-        return note(instruction.asReturn());
-      }
-      if (instruction.isMonitor()) {
-        return note(instruction.asMonitor());
-      }
-      return false;
-    }
-
-    public ParameterUsage build() {
-      return new ParameterUsage(
-          index,
-          ifZeroTestTypes,
-          callsOnReceiver,
-          hasFieldAssignment,
-          hasFieldRead,
-          isAssignedToField,
-          isReturned,
-          isUsedInMonitor);
-    }
-
-    private boolean note(If ifInstruction) {
-      if (ifInstruction.asIf().isZeroTest()) {
-        assert ifInstruction.inValues().size() == 1
-            && ifInstruction.inValues().get(0).getAliasedValue() == arg;
-        ifZeroTestTypes.add(ifInstruction.asIf().getType());
-        return true;
-      }
-      return false;
-    }
-
-    private boolean note(InstanceGet instanceGetInstruction) {
-      assert arg != instanceGetInstruction.outValue();
-      if (instanceGetInstruction.object().getAliasedValue() == arg) {
-        hasFieldRead = true;
-        return true;
-      }
-      return false;
-    }
-
-    private boolean note(InstancePut instancePutInstruction) {
-      assert arg != instancePutInstruction.outValue();
-      if (instancePutInstruction.object().getAliasedValue() == arg) {
-        hasFieldAssignment = true;
-        isAssignedToField |= instancePutInstruction.value().getAliasedValue() == arg;
-        return true;
-      }
-      if (instancePutInstruction.value().getAliasedValue() == arg) {
-        isAssignedToField = true;
-        return true;
-      }
-      return false;
-    }
-
-    private boolean note(InvokeMethodWithReceiver invokeInstruction) {
-      if (ListUtils.lastIndexMatching(
-          invokeInstruction.inValues(), v -> v.getAliasedValue() == arg) == 0) {
-        callsOnReceiver.add(
-            new Pair<>(
-                invokeInstruction.asInvokeMethodWithReceiver().getType(),
-                invokeInstruction.asInvokeMethodWithReceiver().getInvokedMethod()));
-        return true;
-      }
-      return false;
-    }
-
-    private boolean note(InvokeStatic invokeInstruction) {
-      if (invokeInstruction.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
-        if (!invokeInstruction.hasOutValue() || !invokeInstruction.outValue().hasAnyUsers()) {
-          ifZeroTestTypes.add(Type.EQ);
-          return true;
-        }
-      }
-      return false;
-    }
-
-    private boolean note(Return returnInstruction) {
-      assert returnInstruction.inValues().size() == 1
-          && returnInstruction.inValues().get(0).getAliasedValue() == arg;
-      isReturned = true;
-      return true;
-    }
-
-    private boolean note(Monitor monitorInstruction) {
-      assert monitorInstruction.inValues().size() == 1;
-      assert monitorInstruction.inValues().get(0).getAliasedValue() == arg;
-      isUsedInMonitor = true;
-      return true;
-    }
-  }
-}
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 43667c8..d6bdd6b 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
@@ -15,10 +15,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
-import com.android.tools.r8.ir.optimize.classinliner.constraint.AlwaysFalseClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -36,7 +33,7 @@
   private AbstractValue abstractReturnValue =
       DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
   private ClassInlinerMethodConstraint classInlinerConstraint =
-      AlwaysFalseClassInlinerMethodConstraint.getInstance();
+      ClassInlinerMethodConstraint.alwaysFalse();
   private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
   private ClassTypeElement returnsObjectWithLowerBoundType =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
@@ -44,12 +41,8 @@
   // Stores information about instance methods and constructors for
   // class inliner, null value indicates that the method is not eligible.
   private BridgeInfo bridgeInfo = null;
-  private ClassInlinerEligibilityInfo classInlinerEligibility =
-      DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
   private InstanceInitializerInfoCollection instanceInitializerInfoCollection =
       InstanceInitializerInfoCollection.empty();
-  private ParameterUsagesInfo parametersUsages =
-      DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
   // Stores information about nullability hint per parameter. If set, that means, the method
   // somehow (e.g., null check, such as arg != null, or using checkParameterIsNotNull) ensures
   // the corresponding parameter is not null, or throws NPE before any other side effects.
@@ -148,9 +141,7 @@
     inlining = template.inlining;
     simpleInliningConstraint = template.simpleInliningConstraint;
     bridgeInfo = template.bridgeInfo;
-    classInlinerEligibility = template.classInlinerEligibility;
     instanceInitializerInfoCollection = template.instanceInitializerInfoCollection;
-    parametersUsages = template.parametersUsages;
     nonNullParamOrThrow = template.nonNullParamOrThrow;
     nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
   }
@@ -277,11 +268,6 @@
   }
 
   @Override
-  public ParameterUsage getParameterUsages(int parameter) {
-    return parametersUsages == null ? null : parametersUsages.getParameterUsage(parameter);
-  }
-
-  @Override
   public BitSet getNonNullParamOrThrow() {
     return nonNullParamOrThrow;
   }
@@ -331,11 +317,6 @@
   }
 
   @Override
-  public ClassInlinerEligibilityInfo getClassInlinerEligibility() {
-    return classInlinerEligibility;
-  }
-
-  @Override
   public AbstractValue getAbstractReturnValue() {
     return abstractReturnValue;
   }
@@ -380,10 +361,6 @@
     return isFlagSet(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
   }
 
-  void setParameterUsages(ParameterUsagesInfo parametersUsages) {
-    this.parametersUsages = parametersUsages;
-  }
-
   void setNonNullParamOrThrow(BitSet facts) {
     this.nonNullParamOrThrow = facts;
   }
@@ -400,10 +377,6 @@
     this.simpleInliningConstraint = constraint;
   }
 
-  void setClassInlinerEligibility(ClassInlinerEligibilityInfo eligibility) {
-    this.classInlinerEligibility = eligibility;
-  }
-
   void setInstanceInitializerInfoCollection(
       InstanceInitializerInfoCollection instanceInitializerInfoCollection) {
     this.instanceInitializerInfoCollection = instanceInitializerInfoCollection;
@@ -518,6 +491,7 @@
   }
 
   public void adjustOptimizationInfoAfterRemovingThisParameter() {
+    classInlinerConstraint = classInlinerConstraint.fixupAfterRemovingThisParameter();
     // cannotBeKept: doesn't depend on `this`
     // classInitializerMayBePostponed: `this` could trigger <clinit> of the previous holder.
     clearFlag(CLASS_INITIALIZER_MAY_BE_POSTPONED_FLAG);
@@ -550,18 +524,12 @@
     // triggersClassInitBeforeAnySideEffect: code is not changed.
     markTriggerClassInitBeforeAnySideEffect(
         DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT);
-    // classInlinerEligibility: chances are the method is not an instance method anymore.
-    classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
     // initializerInfo: the computed initializer info may become invalid.
     instanceInitializerInfoCollection = InstanceInitializerInfoCollection.empty();
     // initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
     setFlag(
         INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG,
         DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS);
-    parametersUsages =
-        parametersUsages == DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
-            ? DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO
-            : parametersUsages.remove(0);
     nonNullParamOrThrow =
         nonNullParamOrThrow == DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
             ? DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index 1141303..2de1eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
+import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
@@ -57,6 +58,16 @@
   }
 
   private void modelInstanceInitializers() {
+    DexEncodedMethod objectConstructor = lookupMethod(dexItemFactory.objectMembers.constructor);
+    if (objectConstructor != null) {
+      InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+          EmptyInstanceFieldInitializationInfoCollection.getInstance();
+      feedback.setInstanceInitializerInfoCollection(
+          objectConstructor,
+          InstanceInitializerInfoCollection.of(
+              NonTrivialInstanceInitializerInfo.builder(fieldInitializationInfos).build()));
+    }
+
     EnumMembers enumMembers = dexItemFactory.enumMembers;
     DexEncodedMethod enumConstructor = lookupMethod(enumMembers.constructor);
     if (enumConstructor != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index d5ef586..5a2f63a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
@@ -686,8 +687,9 @@
   //  3. Rewrite methods referencing staticized members, also remove instance creation
   //
   public final void staticizeCandidates(
-      OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
-    new StaticizingProcessor(appView, this, converter).run(feedback, executorService);
+      OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
+      throws ExecutionException {
+    new StaticizingProcessor(appView, this, converter, applied).run(feedback, executorService);
   }
 
   private class CallSiteReferencesInvalidator extends UseRegistry {
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 5f54ab6..27806d5 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
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
@@ -85,14 +86,17 @@
   private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
   private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>();
   private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
+  private final GraphLens applied;
 
   StaticizingProcessor(
       AppView<AppInfoWithLiveness> appView,
       ClassStaticizer classStaticizer,
-      IRConverter converter) {
+      IRConverter converter,
+      GraphLens applied) {
     this.appView = appView;
     this.classStaticizer = classStaticizer;
     this.converter = converter;
+    this.applied = applied;
   }
 
   final void run(OptimizationFeedback feedback, ExecutorService executorService)
@@ -233,7 +237,7 @@
         LongLivedProgramMethodSetBuilder<?> referencedFromBuilder =
             classStaticizer.referencedFrom.remove(info);
         assert referencedFromBuilder != null;
-        referencedFrom = referencedFromBuilder.build(appView);
+        referencedFrom = referencedFromBuilder.build(appView, applied);
         materializedReferencedFromCollections.put(info, referencedFrom);
       } else {
         referencedFrom = ProgramMethodSet.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
index 0cafbcd..80066f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
@@ -6,10 +6,10 @@
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunction;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Instruction;
@@ -42,7 +42,7 @@
       Value builder, StringBuilderOptimizationConfiguration configuration) {
     IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
         new IntraproceduralDataflowAnalysis<>(
-            AbstractStateImpl.bottom(), new TransferFunctionImpl(builder, configuration));
+            AbstractStateImpl.bottom(), new TransferFunction(builder, configuration));
     DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
     return result.isFailedAnalysisResult();
   }
@@ -121,13 +121,12 @@
    * <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to
    * bottom.
    */
-  private static class TransferFunctionImpl implements TransferFunction<AbstractStateImpl> {
+  private static class TransferFunction implements AbstractTransferFunction<AbstractStateImpl> {
 
     private final Value builder;
     private final StringBuilderOptimizationConfiguration configuration;
 
-    private TransferFunctionImpl(
-        Value builder, StringBuilderOptimizationConfiguration configuration) {
+    private TransferFunction(Value builder, StringBuilderOptimizationConfiguration configuration) {
       this.builder = builder;
       this.configuration = configuration;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java
new file mode 100644
index 0000000..77462a4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/CallObjectInitCfCodeProvider.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.synthetic;
+
+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.CfReturnVoid;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class CallObjectInitCfCodeProvider extends SyntheticCfCodeProvider {
+
+  public CallObjectInitCfCodeProvider(AppView<?> appView, DexType holder) {
+    super(appView, holder);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    DexItemFactory factory = appView.dexItemFactory();
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, factory.objectMembers.constructor, false));
+    instructions.add(new CfReturnVoid());
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
index b02544d..1f3c6d0 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
@@ -89,6 +89,7 @@
       // Load the argument.
       ValueType fieldType = ValueType.fromDexType(field.getType());
       instructions.add(new CfLoad(fieldType, maxLocals));
+      maxStack += fieldType.requiredRegisters();
       maxLocals += fieldType.requiredRegisters();
     }
 
@@ -102,6 +103,7 @@
       instructions.add(new CfReturnVoid());
     } else {
       ValueType fieldType = ValueType.fromDexType(field.getType());
+      maxStack = Math.max(fieldType.requiredRegisters(), maxStack);
       instructions.add(new CfReturn(fieldType));
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 84acf70..0c6b359 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -182,6 +182,13 @@
       maybeInsertArgumentCast(i, parameter, instructions);
     }
     instructions.add(new CfInvoke(getInvokeOpcode(), targetMethod, isInterface));
+    if (!targetMethod.getReturnType().isVoidType()) {
+      // If the return type is not void, it will push a value on the stack. We subtract the
+      // arguments pushed by the invoke to see if bumping the stack height is necessary.
+      maxStack =
+          Math.max(
+              maxStack, ValueType.fromDexType(targetMethod.getReturnType()).requiredRegisters());
+    }
     if (isSourceReturnVoid()) {
       assert !isConstructorDelegate;
       instructions.add(new CfReturnVoid());
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
new file mode 100644
index 0000000..83fa84e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.naming.NamingLens.NonIdentityNamingLens;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.IdentityHashMap;
+
+// Naming lens for rewriting java.lang.Record to the internal RecordTag type.
+public class RecordRewritingNamingLens extends NonIdentityNamingLens {
+
+  final NamingLens namingLens;
+  private final DexItemFactory factory;
+
+  public static NamingLens createRecordRewritingNamingLens(AppView<?> appView) {
+    return createRecordRewritingNamingLens(appView, NamingLens.getIdentityLens());
+  }
+
+  public static NamingLens createRecordRewritingNamingLens(
+      AppView<?> appView, NamingLens namingLens) {
+    if (!appView.options().shouldDesugarRecords()) {
+      return namingLens;
+    }
+    return new RecordRewritingNamingLens(namingLens, appView);
+  }
+
+  public RecordRewritingNamingLens(NamingLens namingLens, AppView<?> appView) {
+    super(appView.dexItemFactory(), new IdentityHashMap<>());
+    this.namingLens = namingLens;
+    factory = appView.dexItemFactory();
+  }
+
+  private boolean isRenamed(DexType type) {
+    return getRenaming(type) != null;
+  }
+
+  private DexString getRenaming(DexType type) {
+    if (type == factory.recordType) {
+      return factory.r8RecordType.descriptor;
+    }
+    return null;
+  }
+
+  @Override
+  protected DexString internalLookupClassDescriptor(DexType type) {
+    DexString renaming = getRenaming(type);
+    return renaming != null ? renaming : namingLens.lookupDescriptor(type);
+  }
+
+  @Override
+  public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) {
+    assert !isRenamed(attribute.getInner());
+    return namingLens.lookupInnerName(attribute, options);
+  }
+
+  @Override
+  public DexString lookupName(DexMethod method) {
+    // Record rewriting does not influence method name.
+    return namingLens.lookupName(method);
+  }
+
+  @Override
+  public DexString lookupName(DexField field) {
+    // Record rewriting does not influence field name.
+    return namingLens.lookupName(field);
+  }
+
+  @Override
+  public DexString lookupDescriptorForJavaTypeName(String typeName) {
+    if (typeName.equals(factory.recordType.toSourceString())) {
+      return factory.r8RecordType.descriptor;
+    }
+    return namingLens.lookupDescriptorForJavaTypeName(typeName);
+  }
+
+  @Override
+  public String lookupPackageName(String packageName) {
+    return namingLens.lookupPackageName(packageName);
+  }
+
+  @Override
+  public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
+    return namingLens.verifyRenamingConsistentWithResolution(item);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index cdfa649..056abce 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -33,9 +33,11 @@
 import com.google.common.collect.HashBiMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -130,11 +132,13 @@
     }
 
     BiMap<DexType, DexType> mappings = HashBiMap.create();
+    Map<String, String> packageMappings = new HashMap<>();
     Set<String> seenPackageDescriptors = new HashSet<>();
     ProgramPackageCollection packages =
         SortedProgramPackageCollection.createWithAllProgramClasses(appView);
-    processPackagesInDesiredLocation(packages, mappings, seenPackageDescriptors);
-    processRemainingPackages(packages, mappings, seenPackageDescriptors, executorService);
+    processPackagesInDesiredLocation(packages, mappings, packageMappings, seenPackageDescriptors);
+    processRemainingPackages(
+        packages, mappings, packageMappings, seenPackageDescriptors, executorService);
     mappings.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
     if (mappings.isEmpty()) {
       return null;
@@ -146,7 +150,7 @@
         new ArrayList<>(
             repackagingTreeFixer.fixupClasses(appView.appInfo().classesWithDeterministicOrder()));
     appBuilder.replaceProgramClasses(newProgramClasses);
-    RepackagingLens lens = lensBuilder.build(appView);
+    RepackagingLens lens = lensBuilder.build(appView, packageMappings);
     new AnnotationFixer(lens).run(appBuilder.getProgramClasses());
     return lens;
   }
@@ -191,6 +195,7 @@
   private void processPackagesInDesiredLocation(
       ProgramPackageCollection packages,
       BiMap<DexType, DexType> mappings,
+      Map<String, String> packageMappings,
       Set<String> seenPackageDescriptors) {
     // For each package that is already in the desired location, record all the classes from the
     // package in the mapping for collision detection.
@@ -208,6 +213,7 @@
         for (DexProgramClass alreadyRepackagedClass : pkg) {
           processClass(alreadyRepackagedClass, pkg, newPackageDescriptor, mappings);
         }
+        packageMappings.put(pkg.getPackageDescriptor(), newPackageDescriptor);
         seenPackageDescriptors.add(newPackageDescriptor);
         iterator.remove();
       }
@@ -217,6 +223,7 @@
   private void processRemainingPackages(
       ProgramPackageCollection packages,
       BiMap<DexType, DexType> mappings,
+      Map<String, String> packageMappings,
       Set<String> seenPackageDescriptors,
       ExecutorService executorService)
       throws ExecutionException {
@@ -228,13 +235,20 @@
           repackagingConfiguration.getNewPackageDescriptor(pkg, seenPackageDescriptors);
       assert !pkg.getPackageDescriptor().equals(newPackageDescriptor);
 
-      Iterable<DexProgramClass> classesToRepackage =
+      Collection<DexProgramClass> classesToRepackage =
           computeClassesToRepackage(pkg, executorService);
       for (DexProgramClass classToRepackage : classesToRepackage) {
         processClass(classToRepackage, pkg, newPackageDescriptor, mappings);
       }
-
       seenPackageDescriptors.add(newPackageDescriptor);
+      // Package remapping is used for adapting resources. If we cannot repackage all classes in
+      // a package then we put in the original descriptor to ensure that resources are not
+      // rewritten.
+      packageMappings.put(
+          pkg.getPackageDescriptor(),
+          classesToRepackage.size() == pkg.classesInPackage().size()
+              ? newPackageDescriptor
+              : pkg.getPackageDescriptor());
       // TODO(b/165783399): Investigate if repackaging can lead to different dynamic dispatch. See,
       //  for example, CrossPackageInvokeSuperToPackagePrivateMethodTest.
     }
@@ -269,12 +283,12 @@
             classToRepackage, outerClass, newPackageDescriptor, mappings));
   }
 
-  private Iterable<DexProgramClass> computeClassesToRepackage(
+  private Collection<DexProgramClass> computeClassesToRepackage(
       ProgramPackage pkg, ExecutorService executorService) throws ExecutionException {
     RepackagingConstraintGraph constraintGraph = new RepackagingConstraintGraph(appView, pkg);
     boolean canRepackageAllClasses = constraintGraph.initializeGraph();
     if (canRepackageAllClasses) {
-      return pkg;
+      return pkg.classesInPackage();
     }
     constraintGraph.populateConstraints(executorService);
     return constraintGraph.computeClassesToRepackage();
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index ab5b01b..dd6af4a 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -172,7 +173,7 @@
     }
   }
 
-  public Iterable<DexProgramClass> computeClassesToRepackage() {
+  public Collection<DexProgramClass> computeClassesToRepackage() {
     WorkList<Node> worklist = WorkList.newIdentityWorkList(pinnedNodes);
     while (worklist.hasNext()) {
       Node pinnedNode = worklist.next();
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
index 63b8d06..e6d16eb 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -18,16 +18,19 @@
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
+import java.util.Map;
 
 public class RepackagingLens extends NestedGraphLens {
 
   private final BiMap<DexType, DexType> originalTypes;
+  private final Map<String, String> packageRenamings;
 
   private RepackagingLens(
       AppView<AppInfoWithLiveness> appView,
       BidirectionalOneToOneMap<DexField, DexField> newFieldSignatures,
       BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
-      BiMap<DexType, DexType> originalTypes) {
+      BiMap<DexType, DexType> originalTypes,
+      Map<String, String> packageRenamings) {
     super(
         originalTypes.inverse(),
         originalMethodSignatures.getInverseOneToOneMap().getForwardMap(),
@@ -36,6 +39,12 @@
         appView.graphLens(),
         appView.dexItemFactory());
     this.originalTypes = originalTypes;
+    this.packageRenamings = packageRenamings;
+  }
+
+  @Override
+  public String lookupPackageName(String pkg) {
+    return packageRenamings.getOrDefault(getPrevious().lookupPackageName(pkg), pkg);
   }
 
   @Override
@@ -97,10 +106,11 @@
       originalTypes.put(to, from);
     }
 
-    public RepackagingLens build(AppView<AppInfoWithLiveness> appView) {
+    public RepackagingLens build(
+        AppView<AppInfoWithLiveness> appView, Map<String, String> packageRenamings) {
       assert !originalTypes.isEmpty();
       return new RepackagingLens(
-          appView, newFieldSignatures, originalMethodSignatures, originalTypes);
+          appView, newFieldSignatures, originalMethodSignatures, originalTypes, packageRenamings);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index 3c83acd..f032832 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -77,13 +77,17 @@
                   | Constants.ACC_FINAL
                   | Constants.ACC_PUBLIC
                   | Constants.ACC_STATIC);
+      boolean deprecated = false;
+      boolean d8R8Synthesized = true;
       encodedClinitField =
           new DexEncodedField(
               appView.dexItemFactory().createField(clazz.type, clinitField.type, clinitField.name),
               accessFlags,
               FieldTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
-              null);
+              null,
+              deprecated,
+              d8R8Synthesized);
       clazz.appendStaticField(encodedClinitField);
     }
     lensBuilder.map(type, encodedClinitField.field);
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 fd3c069..e818526 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1695,7 +1695,7 @@
 
     assert !mode.isFinalMainDexTracing()
             || !options.testing.checkForNotExpandingMainDexTracingResult
-            || appView.appInfo().getMainDexInfo().isTracedRoot(clazz)
+            || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
             || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
index a60967f..96e263c 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.shaking.MainDexInfo.MainDexGroup.MAIN_DEX_ROOT;
 import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
-import static com.android.tools.r8.utils.PredicateUtils.not;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexMethod;
@@ -17,13 +16,11 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.ConsumerUtils;
-import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.Collections;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
 public class MainDexInfo {
@@ -34,7 +31,6 @@
           Collections.emptySet(),
           Collections.emptySet(),
           Collections.emptySet(),
-          Collections.emptySet(),
           false);
 
   public enum MainDexGroup {
@@ -46,7 +42,6 @@
 
   // Specific set of classes specified to be in main-dex
   private final Set<DexType> classList;
-  private final Map<DexType, DexType> synthesizedClassesMap;
   // Traced roots are traced main dex references.
   private final Set<DexType> tracedRoots;
   // Traced method roots are the methods visited from an initial main dex root set. The set is
@@ -64,7 +59,6 @@
         Collections.emptySet(),
         Collections.emptySet(),
         Collections.emptySet(),
-        /* synthesized classes - cannot be emptyset */ Sets.newIdentityHashSet(),
         false);
   }
 
@@ -73,17 +67,18 @@
       Set<DexType> tracedRoots,
       Set<DexMethod> tracedMethodRoots,
       Set<DexType> tracedDependencies,
-      Set<DexType> synthesizedClasses,
       boolean tracedMethodRootsCleared) {
     this.classList = classList;
     this.tracedRoots = tracedRoots;
     this.tracedMethodRoots = tracedMethodRoots;
     this.tracedDependencies = tracedDependencies;
-    this.synthesizedClassesMap = new ConcurrentHashMap<>();
     this.tracedMethodRootsCleared = tracedMethodRootsCleared;
-    synthesizedClasses.forEach(type -> synthesizedClassesMap.put(type, type));
     assert tracedDependencies.stream().noneMatch(tracedRoots::contains);
-    assert tracedRoots.containsAll(synthesizedClasses);
+  }
+
+  // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+  public boolean isSyntheticContextOnMainDexList(DexType syntheticContextType) {
+    return classList.contains(syntheticContextType);
   }
 
   public boolean isNone() {
@@ -91,25 +86,16 @@
     return this == NONE;
   }
 
-  public boolean isMainDexTypeThatShouldIncludeDependencies(DexType type) {
-    // Dependencies of 'type' are only needed if 'type' is a direct/executed main-dex type.
-    return classList.contains(type) || tracedRoots.contains(type);
+  public boolean isFromList(ProgramDefinition definition, SyntheticItems synthetics) {
+    return isFromList(definition.getContextType(), synthetics);
   }
 
-  public boolean isMainDex(ProgramDefinition definition) {
-    return isFromList(definition) || isTracedRoot(definition) || isDependency(definition);
+  private boolean isFromList(DexReference reference, SyntheticItems synthetics) {
+    return isContainedOrHasContainedContext(reference, classList, synthetics);
   }
 
-  public boolean isFromList(ProgramDefinition definition) {
-    return isFromList(definition.getContextType());
-  }
-
-  private boolean isFromList(DexReference reference) {
-    return classList.contains(reference.getContextType());
-  }
-
-  public boolean isTracedRoot(ProgramDefinition definition) {
-    return isTracedRoot(definition.getContextType());
+  public boolean isTracedRoot(ProgramDefinition definition, SyntheticItems synthetics) {
+    return isTracedRoot(definition.getContextType(), synthetics);
   }
 
   public boolean isTracedMethodRoot(DexMethod method) {
@@ -117,8 +103,22 @@
     return tracedMethodRoots.contains(method);
   }
 
-  private boolean isTracedRoot(DexReference reference) {
-    return tracedRoots.contains(reference.getContextType());
+  private boolean isTracedRoot(DexReference reference, SyntheticItems synthetics) {
+    return isContainedOrHasContainedContext(reference, tracedRoots, synthetics);
+  }
+
+  private boolean isContainedOrHasContainedContext(
+      DexReference reference, Set<DexType> items, SyntheticItems synthetics) {
+    if (items.isEmpty()) {
+      return false;
+    }
+    DexType type = reference.getContextType();
+    for (DexType context : synthetics.getSynthesizingContexts(type)) {
+      if (items.contains(context)) {
+        return true;
+      }
+    }
+    return items.contains(type);
   }
 
   private boolean isDependency(ProgramDefinition definition) {
@@ -138,8 +138,9 @@
     this.tracedMethodRoots = Sets.newIdentityHashSet();
   }
 
-  public boolean canRebindReference(ProgramMethod context, DexReference referenceToTarget) {
-    MainDexGroup holderGroup = getMainDexGroupInternal(context);
+  public boolean canRebindReference(
+      ProgramMethod context, DexReference referenceToTarget, SyntheticItems synthetics) {
+    MainDexGroup holderGroup = getMainDexGroupInternal(context, synthetics);
     if (holderGroup == MainDexGroup.NOT_IN_MAIN_DEX
         || holderGroup == MainDexGroup.MAIN_DEX_DEPENDENCY) {
       // We are always free to rebind/inline into something not in main-dex or traced dependencies.
@@ -152,20 +153,21 @@
     }
     assert holderGroup == MAIN_DEX_ROOT;
     // Otherwise we allow if either is both root.
-    return getMainDexGroupInternal(referenceToTarget) == MAIN_DEX_ROOT;
+    return getMainDexGroupInternal(referenceToTarget, synthetics) == MAIN_DEX_ROOT;
   }
 
-  public boolean canMerge(ProgramDefinition candidate) {
-    return !isFromList(candidate);
+  public boolean canMerge(ProgramDefinition candidate, SyntheticItems synthetics) {
+    return !isFromList(candidate, synthetics);
   }
 
-  public boolean canMerge(ProgramDefinition source, ProgramDefinition target) {
-    return canMerge(source.getContextType(), target.getContextType());
+  public boolean canMerge(
+      ProgramDefinition source, ProgramDefinition target, SyntheticItems synthetics) {
+    return canMerge(source.getContextType(), target.getContextType(), synthetics);
   }
 
-  private boolean canMerge(DexReference source, DexReference target) {
-    MainDexGroup sourceGroup = getMainDexGroupInternal(source);
-    MainDexGroup targetGroup = getMainDexGroupInternal(target);
+  private boolean canMerge(DexReference source, DexReference target, SyntheticItems synthetics) {
+    MainDexGroup sourceGroup = getMainDexGroupInternal(source, synthetics);
+    MainDexGroup targetGroup = getMainDexGroupInternal(target, synthetics);
     if (sourceGroup != targetGroup) {
       return false;
     }
@@ -174,20 +176,22 @@
     return sourceGroup != MainDexGroup.MAIN_DEX_LIST;
   }
 
-  public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate) {
-    MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate);
+  public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate, SyntheticItems synthetics) {
+    assert canMerge(mergeCandidate, synthetics);
+    MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate, synthetics);
     return mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST ? null : mainDexGroupInternal;
   }
 
-  private MainDexGroup getMainDexGroupInternal(ProgramDefinition definition) {
-    return getMainDexGroupInternal(definition.getReference());
+  private MainDexGroup getMainDexGroupInternal(
+      ProgramDefinition definition, SyntheticItems synthetics) {
+    return getMainDexGroupInternal(definition.getReference(), synthetics);
   }
 
-  private MainDexGroup getMainDexGroupInternal(DexReference reference) {
-    if (isFromList(reference)) {
+  private MainDexGroup getMainDexGroupInternal(DexReference reference, SyntheticItems synthetics) {
+    if (isFromList(reference, synthetics)) {
       return MainDexGroup.MAIN_DEX_LIST;
     }
-    if (isTracedRoot(reference)) {
+    if (isTracedRoot(reference, synthetics)) {
       return MAIN_DEX_ROOT;
     }
     if (isDependency(reference)) {
@@ -197,22 +201,25 @@
   }
 
   public boolean disallowInliningIntoContext(
-      AppInfoWithClassHierarchy appInfo, ProgramDefinition context, ProgramMethod method) {
+      AppInfoWithClassHierarchy appInfo,
+      ProgramDefinition context,
+      ProgramMethod method,
+      SyntheticItems synthetics) {
     if (context.getContextType() == method.getContextType()) {
       return false;
     }
-    MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context);
+    MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context, synthetics);
     if (mainDexGroupInternal == MainDexGroup.NOT_IN_MAIN_DEX
         || mainDexGroupInternal == MainDexGroup.MAIN_DEX_DEPENDENCY) {
       return false;
     }
     if (mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST) {
       return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses(
-          appInfo, method, not(this::isFromList));
+          appInfo, method, t -> !isFromList(t, synthetics));
     }
     assert mainDexGroupInternal == MAIN_DEX_ROOT;
     return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses(
-        appInfo, method, not(this::isTracedRoot));
+        appInfo, method, t -> !isTracedRoot(t, synthetics));
   }
 
   public boolean isEmpty() {
@@ -224,35 +231,6 @@
     return NONE;
   }
 
-  // TODO(b/178127572): This mutates the MainDexClasses which otherwise should be immutable.
-  public void addSyntheticClass(DexProgramClass clazz) {
-    // TODO(b/178127572): This will add a synthesized type as long as the initial set is not empty.
-    //  A better approach would be to use the context for the synthetic with a containment check.
-    assert !isNone();
-    if (!classList.isEmpty()) {
-      synthesizedClassesMap.computeIfAbsent(
-          clazz.type,
-          type -> {
-            classList.add(type);
-            return type;
-          });
-    }
-    if (!tracedRoots.isEmpty()) {
-      synthesizedClassesMap.computeIfAbsent(
-          clazz.type,
-          type -> {
-            tracedRoots.add(type);
-            return type;
-          });
-    }
-  }
-
-  public void addLegacySyntheticClass(DexProgramClass clazz, ProgramDefinition context) {
-    if (isTracedRoot(context) || isFromList(context) || isDependency(context)) {
-      addSyntheticClass(clazz);
-    }
-  }
-
   public int size() {
     return classList.size() + tracedRoots.size() + tracedDependencies.size();
   }
@@ -278,11 +256,7 @@
     }
     Set<DexType> removedClasses = prunedItems.getRemovedClasses();
     Set<DexType> modifiedClassList = Sets.newIdentityHashSet();
-    Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet();
     classList.forEach(type -> ifNotRemoved(type, removedClasses, modifiedClassList::add));
-    synthesizedClassesMap
-        .keySet()
-        .forEach(type -> ifNotRemoved(type, removedClasses, modifiedSynthesized::add));
     MainDexInfo.Builder builder = builder();
     tracedRoots.forEach(type -> ifNotRemoved(type, removedClasses, builder::addRoot));
     // TODO(b/169927809): Methods could be pruned without the holder being pruned, however, one has
@@ -292,7 +266,7 @@
             ifNotRemoved(
                 method.getHolderType(), removedClasses, ignored -> builder.addRoot(method)));
     tracedDependencies.forEach(type -> ifNotRemoved(type, removedClasses, builder::addDependency));
-    return builder.build(modifiedClassList, modifiedSynthesized);
+    return builder.build(modifiedClassList);
   }
 
   private void ifNotRemoved(
@@ -304,10 +278,8 @@
 
   public MainDexInfo rewrittenWithLens(GraphLens lens) {
     Set<DexType> modifiedClassList = Sets.newIdentityHashSet();
-    Set<DexType> modifiedSynthesized = Sets.newIdentityHashSet();
     classList.forEach(
         type -> rewriteAndApplyIfNotPrimitiveType(lens, type, modifiedClassList::add));
-    synthesizedClassesMap.keySet().forEach(type -> modifiedSynthesized.add(lens.lookupType(type)));
     MainDexInfo.Builder builder = builder();
     tracedRoots.forEach(type -> rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addRoot));
     tracedMethodRoots.forEach(method -> builder.addRoot(lens.getRenamedMethodSignature(method)));
@@ -321,7 +293,7 @@
             rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependency);
           }
         });
-    return builder.build(modifiedClassList, modifiedSynthesized);
+    return builder.build(modifiedClassList);
   }
 
   public Builder builder() {
@@ -413,20 +385,27 @@
       return new MainDexInfo(list);
     }
 
-    public MainDexInfo build(Set<DexType> classList, Set<DexType> synthesizedClasses) {
+    public MainDexInfo build(Set<DexType> classList) {
       // Class can contain dependencies which we should not regard as roots.
       assert list.isEmpty();
-      return new MainDexInfo(
-          classList,
-          SetUtils.unionIdentityHashSet(roots, synthesizedClasses),
-          methodRoots,
-          Sets.difference(dependencies, synthesizedClasses),
-          synthesizedClasses,
-          tracedMethodRootsCleared);
+      return new MainDexInfo(classList, roots, methodRoots, dependencies, tracedMethodRootsCleared);
     }
 
     public MainDexInfo build(MainDexInfo previous) {
-      return build(previous.classList, previous.synthesizedClassesMap.keySet());
+      return build(previous.classList);
     }
+
+    public MainDexInfo build() {
+      return new MainDexInfo(list, roots, methodRoots, dependencies, tracedMethodRootsCleared);
+    }
+  }
+
+  public Builder builderFromCopy() {
+    Builder builder = new Builder(tracedMethodRootsCleared);
+    builder.list.addAll(classList);
+    builder.roots.addAll(tracedRoots);
+    builder.methodRoots.addAll(tracedMethodRoots);
+    builder.dependencies.addAll(tracedDependencies);
+    return builder;
   }
 }
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 afc9e50..761b314 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -826,7 +826,7 @@
     }
 
     // Check with main dex classes to see if we are allowed to merge.
-    if (!mainDexInfo.canMerge(clazz, targetClass)) {
+    if (!mainDexInfo.canMerge(clazz, targetClass, appView.getSyntheticItems())) {
       return;
     }
 
@@ -1657,7 +1657,8 @@
         }
         // Constructors can have references beyond the root main dex classes. This can increase the
         // size of the main dex dependent classes and we should bail out.
-        if (mainDexInfo.disallowInliningIntoContext(appView.appInfo(), context, method)) {
+        if (mainDexInfo.disallowInliningIntoContext(
+            appView.appInfo(), context, method, appView.getSyntheticItems())) {
           return AbortReason.MAIN_DEX_ROOT_OUTSIDE_REFERENCE;
         }
         return null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
index 2681a90..adc1298 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -56,7 +56,7 @@
 
   @Deprecated
   public Collection<DexType> getLegacySyntheticTypes() {
-    return committed.getLegacyTypes();
+    return committed.getLegacyTypes().keySet();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index 053b20b..7dbdec5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -4,12 +4,11 @@
 package com.android.tools.r8.synthesis;
 
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.utils.BooleanBox;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Map;
@@ -29,7 +28,7 @@
     private ImmutableMap.Builder<DexType, SyntheticProgramClassReference> newNonLegacyClasses =
         null;
     private ImmutableMap.Builder<DexType, SyntheticMethodReference> newNonLegacyMethods = null;
-    private ImmutableSet.Builder<DexType> newLegacyClasses = null;
+    private ImmutableMap.Builder<DexType, LegacySyntheticReference> newLegacyClasses = null;
 
     public Builder(CommittedSyntheticsCollection parent) {
       this.parent = parent;
@@ -66,19 +65,19 @@
       return this;
     }
 
-    public Builder addLegacyClasses(Collection<DexProgramClass> classes) {
+    public Builder addLegacyClasses(Map<DexType, LegacySyntheticDefinition> classes) {
       if (newLegacyClasses == null) {
-        newLegacyClasses = ImmutableSet.builder();
+        newLegacyClasses = ImmutableMap.builder();
       }
-      classes.forEach(c -> newLegacyClasses.add(c.getType()));
+      classes.forEach((type, item) -> newLegacyClasses.put(type, item.toReference()));
       return this;
     }
 
-    public Builder addLegacyClass(DexType type) {
+    public Builder addLegacyClass(LegacySyntheticReference item) {
       if (newLegacyClasses == null) {
-        newLegacyClasses = ImmutableSet.builder();
+        newLegacyClasses = ImmutableMap.builder();
       }
-      newLegacyClasses.add(type);
+      newLegacyClasses.put(item.getHolder(), item);
       return this;
     }
 
@@ -94,24 +93,24 @@
           newNonLegacyMethods == null
               ? parent.nonLegacyMethods
               : newNonLegacyMethods.putAll(parent.nonLegacyMethods).build();
-      ImmutableSet<DexType> allLegacyClasses =
+      ImmutableMap<DexType, LegacySyntheticReference> allLegacyClasses =
           newLegacyClasses == null
               ? parent.legacyTypes
-              : newLegacyClasses.addAll(parent.legacyTypes).build();
+              : newLegacyClasses.putAll(parent.legacyTypes).build();
       return new CommittedSyntheticsCollection(
           allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses);
     }
   }
 
   private static final CommittedSyntheticsCollection EMPTY =
-      new CommittedSyntheticsCollection(ImmutableSet.of(), ImmutableMap.of(), ImmutableMap.of());
+      new CommittedSyntheticsCollection(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of());
 
   /**
    * Immutable set of synthetic types in the application (eg, committed).
    *
    * <p>TODO(b/158159959): Remove legacy support.
    */
-  private final ImmutableSet<DexType> legacyTypes;
+  private final ImmutableMap<DexType, LegacySyntheticReference> legacyTypes;
 
   /** Mapping from synthetic type to its synthetic method item description. */
   private final ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods;
@@ -120,14 +119,16 @@
   private final ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses;
 
   public CommittedSyntheticsCollection(
-      ImmutableSet<DexType> legacyTypes,
+      ImmutableMap<DexType, LegacySyntheticReference> legacyTypes,
       ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods,
       ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses) {
     this.legacyTypes = legacyTypes;
     this.nonLegacyMethods = nonLegacyMethods;
     this.nonLegacyClasses = nonLegacyClasses;
     assert legacyTypes.size() + nonLegacyMethods.size() + nonLegacyClasses.size()
-        == Sets.union(Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()), legacyTypes)
+        == Sets.union(
+                Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()),
+                legacyTypes.keySet())
             .size();
   }
 
@@ -148,14 +149,14 @@
   }
 
   public boolean containsLegacyType(DexType type) {
-    return legacyTypes.contains(type);
+    return legacyTypes.containsKey(type);
   }
 
   public boolean containsNonLegacyType(DexType type) {
     return nonLegacyMethods.containsKey(type) || nonLegacyClasses.containsKey(type);
   }
 
-  public ImmutableSet<DexType> getLegacyTypes() {
+  public ImmutableMap<DexType, LegacySyntheticReference> getLegacyTypes() {
     return legacyTypes;
   }
 
@@ -186,39 +187,40 @@
       return this;
     }
     Builder builder = CommittedSyntheticsCollection.empty().builder();
-    boolean changed = false;
-    for (DexType type : legacyTypes) {
-      if (removed.contains(type)) {
-        changed = true;
-      } else {
-        builder.addLegacyClass(type);
-      }
-    }
+    BooleanBox changed = new BooleanBox(false);
+    legacyTypes.forEach(
+        (type, item) -> {
+          if (removed.contains(type)) {
+            changed.set();
+          } else {
+            builder.addLegacyClass(item);
+          }
+        });
     for (SyntheticMethodReference reference : nonLegacyMethods.values()) {
       if (removed.contains(reference.getHolder())) {
-        changed = true;
+        changed.set();
       } else {
         builder.addNonLegacyMethod(reference);
       }
     }
     for (SyntheticProgramClassReference reference : nonLegacyClasses.values()) {
       if (removed.contains(reference.getHolder())) {
-        changed = true;
+        changed.set();
       } else {
         builder.addNonLegacyClass(reference);
       }
     }
-    return changed ? builder.build() : this;
+    return changed.isTrue() ? builder.build() : this;
   }
 
   CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) {
     return new CommittedSyntheticsCollection(
-        lens.rewriteTypes(legacyTypes),
+        rewriteItems(legacyTypes, lens),
         rewriteItems(nonLegacyMethods, lens),
         rewriteItems(nonLegacyClasses, lens));
   }
 
-  private static <R extends SyntheticReference<R, ?, ?>> ImmutableMap<DexType, R> rewriteItems(
+  private static <R extends Rewritable<R>> ImmutableMap<DexType, R> rewriteItems(
       Map<DexType, R> items, NonIdentityGraphLens lens) {
     ImmutableMap.Builder<DexType, R> rewrittenItems = ImmutableMap.builder();
     for (R reference : items.values()) {
@@ -231,7 +233,7 @@
   }
 
   boolean verifyTypesAreInApp(DexApplication application) {
-    assert verifyTypesAreInApp(application, legacyTypes);
+    assert verifyTypesAreInApp(application, legacyTypes.keySet());
     assert verifyTypesAreInApp(application, nonLegacyMethods.keySet());
     assert verifyTypesAreInApp(application, nonLegacyClasses.keySet());
     return true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java
new file mode 100644
index 0000000..47a9556
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticDefinition.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class LegacySyntheticDefinition {
+  private final DexProgramClass clazz;
+  private final Map<DexType, DexType> contexts = new ConcurrentHashMap<>();
+
+  public LegacySyntheticDefinition(DexProgramClass clazz) {
+    this.clazz = clazz;
+  }
+
+  public void addContext(ProgramDefinition clazz) {
+    DexType type = clazz.getContextType();
+    contexts.put(type, type);
+  }
+
+  public Set<DexType> getContexts() {
+    return contexts.keySet();
+  }
+
+  public LegacySyntheticReference toReference() {
+    return new LegacySyntheticReference(clazz.getType(), ImmutableSet.copyOf(contexts.keySet()));
+  }
+
+  public DexProgramClass getDefinition() {
+    return clazz;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java
new file mode 100644
index 0000000..06d2e2c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/LegacySyntheticReference.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import java.util.Set;
+
+public class LegacySyntheticReference implements Rewritable<LegacySyntheticReference> {
+  private final DexType type;
+  private final Set<DexType> contexts;
+
+  public LegacySyntheticReference(DexType type, Set<DexType> contexts) {
+    this.type = type;
+    this.contexts = contexts;
+  }
+
+  @Override
+  public DexType getHolder() {
+    return type;
+  }
+
+  public Set<DexType> getContexts() {
+    return contexts;
+  }
+
+  @Override
+  public LegacySyntheticReference rewrite(NonIdentityGraphLens lens) {
+    DexType rewrittenType = lens.lookupType(type);
+    Set<DexType> rewrittenContexts = lens.rewriteTypes(getContexts());
+    if (type == rewrittenType && contexts.equals(rewrittenContexts)) {
+      return this;
+    }
+    return new LegacySyntheticReference(rewrittenType, rewrittenContexts);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/Rewritable.java b/src/main/java/com/android/tools/r8/synthesis/Rewritable.java
new file mode 100644
index 0000000..408b3a3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/Rewritable.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+
+public interface Rewritable<R extends Rewritable<R>> {
+
+  DexType getHolder();
+
+  R rewrite(NonIdentityGraphLens lens);
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index bff9202..04ec40a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -44,6 +44,10 @@
         context.getContextType(), context.getContextType(), context.getOrigin());
   }
 
+  static SynthesizingContext fromType(DexType type) {
+    return new SynthesizingContext(type, type, Origin.unknown());
+  }
+
   static SynthesizingContext fromNonSyntheticInputContext(ProgramDefinition context) {
     // A context that is itself non-synthetic is the single context, thus both the input context
     // and synthesizing context coincide.
@@ -122,21 +126,13 @@
     appView.rewritePrefix.rewriteType(hygienicType, rewrittenType);
   }
 
-  // TODO(b/180074885): Remove this once main-dex is traced at the end of of compilation.
-  void addIfDerivedFromMainDexClass(
-      DexProgramClass externalSyntheticClass, MainDexInfo mainDexInfo) {
-    if (mainDexInfo.isMainDex(externalSyntheticClass)) {
-      return;
-    }
-    // The input context type (not the annotated context) determines if the derived class is to be
-    // in main dex, as it is the input context type that is traced as part of main-dex tracing.
-    if (mainDexInfo.isMainDexTypeThatShouldIncludeDependencies(inputContextType)) {
-      mainDexInfo.addSyntheticClass(externalSyntheticClass);
-    }
-  }
-
   @Override
   public String toString() {
     return "SynthesizingContext{" + getSynthesizingContextType() + "}";
   }
+
+  // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+  boolean isDerivedFromMainDexList(MainDexInfo mainDexInfo) {
+    return mainDexInfo.isSyntheticContextOnMainDexList(inputContextType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 01e6c75..126355f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -34,6 +34,7 @@
   private final DexType type;
   private final Origin origin;
 
+  private boolean isAbstract = false;
   private Kind originKind;
   private DexType superType;
   private DexTypeList interfaces = DexTypeList.empty();
@@ -70,6 +71,11 @@
     return self();
   }
 
+  public B setAbstract() {
+    isAbstract = true;
+    return self();
+  }
+
   public B setOriginKind(Kind originKind) {
     this.originKind = originKind;
     return self();
@@ -107,9 +113,10 @@
   }
 
   public C build() {
+    int flag = isAbstract ? Constants.ACC_ABSTRACT : Constants.ACC_FINAL;
     ClassAccessFlags accessFlags =
         ClassAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_FINAL | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+            flag | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
     DexString sourceFile = null;
     NestHostClassAttribute nestHost = null;
     List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
index 69f87fe..92ac183 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  DexType getHolder() {
+  public DexType getHolder() {
     return type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 2e70db6..d610175 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -29,7 +31,8 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
@@ -43,10 +46,8 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.BiConsumer;
-import java.util.function.Function;
 
 public class SyntheticFinalization {
 
@@ -54,129 +55,76 @@
     public final CommittedItems commit;
     public final NonIdentityGraphLens lens;
     public final PrunedItems prunedItems;
+    public final MainDexInfo mainDexInfo;
 
     public Result(
-        CommittedItems commit, SyntheticFinalizationGraphLens lens, PrunedItems prunedItems) {
+        CommittedItems commit,
+        SyntheticFinalizationGraphLens lens,
+        PrunedItems prunedItems,
+        MainDexInfo mainDexInfo) {
       this.commit = commit;
       this.lens = lens;
       this.prunedItems = prunedItems;
+      this.mainDexInfo = mainDexInfo;
     }
   }
 
   public static class SyntheticFinalizationGraphLens extends NestedGraphLens {
 
-    private final Map<DexType, DexType> syntheticTypeMap;
-    private final Map<DexMethod, DexMethod> syntheticMethodsMap;
-
     private SyntheticFinalizationGraphLens(
         GraphLens previous,
-        Map<DexType, DexType> syntheticClassesMap,
-        Map<DexMethod, DexMethod> syntheticMethodsMap,
         Map<DexType, DexType> typeMap,
         BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
         Map<DexMethod, DexMethod> methodMap,
         BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
         DexItemFactory factory) {
       super(typeMap, methodMap, fieldMap, originalMethodSignatures, previous, factory);
-      this.syntheticTypeMap = syntheticClassesMap;
-      this.syntheticMethodsMap = syntheticMethodsMap;
     }
 
     @Override
     public boolean isSyntheticFinalizationGraphLens() {
       return true;
     }
-
-    // The mapping is many to one, so the inverse is only defined up to equivalence groups.
-    // Override the access to renamed signatures to first check for synthetic mappings before
-    // using the original item mappings of the
-
-    @Override
-    public DexField getRenamedFieldSignature(DexField originalField) {
-      if (syntheticTypeMap.containsKey(originalField.holder)) {
-        DexField renamed = fieldMap.get(originalField);
-        if (renamed != null) {
-          return renamed;
-        }
-      }
-      return super.getRenamedFieldSignature(originalField);
-    }
-
-    @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
-      if (syntheticTypeMap.containsKey(originalMethod.holder)) {
-        DexMethod renamed = methodMap.get(originalMethod);
-        if (renamed != null) {
-          return renamed;
-        }
-      }
-      DexMethod renamed = syntheticMethodsMap.get(originalMethod);
-      return renamed != null ? renamed : super.getRenamedMethodSignature(originalMethod, applied);
-    }
   }
 
   private static class Builder {
 
-    // Forward mapping of internal to external synthetics.
-    Map<DexType, DexType> syntheticClassesMap = new IdentityHashMap<>();
-    Map<DexMethod, DexMethod> syntheticMethodsMap = new IdentityHashMap<>();
-
     Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     BidirectionalManyToOneRepresentativeHashMap<DexField, DexField> fieldMap =
         new BidirectionalManyToOneRepresentativeHashMap<>();
     Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
 
-    protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
-        new BidirectionalOneToOneHashMap<>();
-
-    void moveSyntheticClass(DexType from, DexType to) {
-      assert !syntheticClassesMap.containsKey(from);
-      syntheticClassesMap.put(from, to);
-      typeMap.put(from, to);
-    }
-
-    void moveSyntheticMethod(DexMethod from, DexMethod to) {
-      assert !syntheticMethodsMap.containsKey(from);
-      syntheticMethodsMap.put(from, to);
-      methodMap.put(from, to);
-      typeMap.put(from.getHolderType(), to.getHolderType());
-    }
+    protected final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod>
+        originalMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
 
     void move(DexType from, DexType to) {
-      typeMap.put(from, to);
+      DexType old = typeMap.put(from, to);
+      assert old == null || old == to;
     }
 
     void move(DexField from, DexField to) {
-      fieldMap.put(from, to);
+      DexField old = fieldMap.put(from, to);
+      assert old == null || old == to;
     }
 
     void move(DexMethod from, DexMethod to) {
-      methodMap.put(from, to);
+      DexMethod old = methodMap.put(from, to);
+      assert old == null || old == to;
       originalMethodSignatures.put(to, from);
     }
 
     SyntheticFinalizationGraphLens build(GraphLens previous, DexItemFactory factory) {
-      assert verifySubMap(syntheticClassesMap, typeMap);
       if (typeMap.isEmpty() && fieldMap.isEmpty() && methodMap.isEmpty()) {
         return null;
       }
       return new SyntheticFinalizationGraphLens(
           previous,
-          syntheticClassesMap,
-          syntheticMethodsMap,
           typeMap,
           fieldMap,
           methodMap,
           originalMethodSignatures,
           factory);
     }
-
-    private static <K, V> boolean verifySubMap(Map<K, V> sub, Map<K, V> sup) {
-      for (Entry<K, V> entry : sub.entrySet()) {
-        assert sup.get(entry.getKey()) == entry.getValue();
-      }
-      return true;
-    }
   }
 
   public static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> {
@@ -231,26 +179,38 @@
     assert !appView.appInfo().hasClassHierarchy();
     assert !appView.appInfo().hasLiveness();
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
-    appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexInfo()));
-    appView.pruneItems(result.prunedItems);
+    appView.setAppInfo(new AppInfo(result.commit, result.mainDexInfo));
     if (result.lens != null) {
+      appView.setAppInfo(
+          appView
+              .appInfo()
+              .rebuildWithMainDexInfo(
+                  appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens)));
       appView.setGraphLens(result.lens);
     }
+    appView.pruneItems(result.prunedItems);
   }
 
   public static void finalizeWithClassHierarchy(AppView<AppInfoWithClassHierarchy> appView) {
     assert !appView.appInfo().hasLiveness();
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
     appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
-    appView.pruneItems(result.prunedItems);
+    appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
     if (result.lens != null) {
       appView.setGraphLens(result.lens);
+      appView.setAppInfo(
+          appView
+              .appInfo()
+              .rebuildWithMainDexInfo(
+                  appView.appInfo().getMainDexInfo().rewrittenWithLens(result.lens)));
     }
+    appView.pruneItems(result.prunedItems);
   }
 
   public static void finalizeWithLiveness(AppView<AppInfoWithLiveness> appView) {
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
     appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
+    appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
     appView.rewriteWithLens(result.lens);
     appView.pruneItems(result.prunedItems);
   }
@@ -263,23 +223,20 @@
         ImmutableMap.builder();
     ImmutableMap.Builder<DexType, SyntheticProgramClassReference> finalClassesBuilder =
         ImmutableMap.builder();
-    List<DexProgramClass> finalSyntheticProgramDefinitions = new ArrayList<>();
+    Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
     {
       Map<String, NumberGenerator> generators = new HashMap<>();
       application =
           buildLensAndProgram(
               appView,
-              computeEquivalences(appView, committed.getNonLegacyMethods().values(), generators),
-              computeEquivalences(appView, committed.getNonLegacyClasses().values(), generators),
+              computeEquivalences(
+                  appView, committed.getNonLegacyMethods().values(), generators, lensBuilder),
+              computeEquivalences(
+                  appView, committed.getNonLegacyClasses().values(), generators, lensBuilder),
               lensBuilder,
-              (clazz, reference) -> {
-                finalSyntheticProgramDefinitions.add(clazz);
-                finalClassesBuilder.put(clazz.getType(), reference);
-              },
-              (clazz, reference) -> {
-                finalSyntheticProgramDefinitions.add(clazz);
-                finalMethodsBuilder.put(clazz.getType(), reference);
-              });
+              (clazz, reference) -> finalClassesBuilder.put(clazz.getType(), reference),
+              (clazz, reference) -> finalMethodsBuilder.put(clazz.getType(), reference),
+              derivedMainDexTypes);
     }
     ImmutableMap<DexType, SyntheticMethodReference> finalMethods = finalMethodsBuilder.build();
     ImmutableMap<DexType, SyntheticProgramClassReference> finalClasses =
@@ -294,6 +251,10 @@
           }
         });
 
+    // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+    MainDexInfo.Builder mainDexInfoBuilder = appView.appInfo().getMainDexInfo().builderFromCopy();
+    derivedMainDexTypes.forEach(mainDexInfoBuilder::addList);
+
     return new Result(
         new CommittedItems(
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
@@ -302,17 +263,16 @@
                 committed.getLegacyTypes(), finalMethods, finalClasses),
             ImmutableList.of()),
         lensBuilder.build(appView.graphLens(), appView.dexItemFactory()),
-        PrunedItems.builder()
-            .setPrunedApp(application)
-            .addRemovedClasses(prunedSynthetics)
-            .build());
+        PrunedItems.builder().setPrunedApp(application).addRemovedClasses(prunedSynthetics).build(),
+        mainDexInfoBuilder.build());
   }
 
   private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
       Map<DexType, EquivalenceGroup<D>> computeEquivalences(
           AppView<?> appView,
           ImmutableCollection<R> references,
-          Map<String, NumberGenerator> generators) {
+          Map<String, NumberGenerator> generators,
+          Builder lensBuilder) {
     boolean intermediate = appView.options().intermediate;
     Map<DexType, D> definitions = lookupDefinitions(appView, references);
     ClassToFeatureSplitMap classToFeatureSplitMap =
@@ -328,7 +288,12 @@
             classToFeatureSplitMap,
             synthetics);
     return computeActualEquivalences(
-        potentialEquivalences, generators, appView, intermediate, classToFeatureSplitMap);
+        potentialEquivalences,
+        generators,
+        appView,
+        intermediate,
+        classToFeatureSplitMap,
+        lensBuilder);
   }
 
   private boolean isNotSyntheticType(DexType type) {
@@ -339,7 +304,8 @@
     // Check that a context is never itself synthetic class.
     committed.forEachNonLegacyItem(
         item -> {
-          assert isNotSyntheticType(item.getContext().getSynthesizingContextType());
+          assert isNotSyntheticType(item.getContext().getSynthesizingContextType())
+              || item.getKind().allowSyntheticContext();
         });
     return true;
   }
@@ -350,75 +316,18 @@
       Map<DexType, EquivalenceGroup<SyntheticProgramClassDefinition>> syntheticClassGroups,
       Builder lensBuilder,
       BiConsumer<DexProgramClass, SyntheticProgramClassReference> addFinalSyntheticClass,
-      BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod) {
+      BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod,
+      Set<DexType> derivedMainDexSynthetics) {
     DexApplication application = appView.appInfo().app();
-    DexItemFactory factory = appView.dexItemFactory();
+    MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
     List<DexProgramClass> newProgramClasses = new ArrayList<>();
     Set<DexType> pruned = Sets.newIdentityHashSet();
 
-    syntheticMethodGroups.forEach(
-        (syntheticType, syntheticGroup) -> {
-          SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
-          SynthesizingContext context = representative.getContext();
-          context.registerPrefixRewriting(syntheticType, appView);
-          DexProgramClass externalSyntheticClass =
-              createExternalMethodClass(syntheticType, representative, factory);
-          newProgramClasses.add(externalSyntheticClass);
-          addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
-          assert externalSyntheticClass.getMethodCollection().size() == 1;
-          DexEncodedMethod externalSyntheticMethod =
-              externalSyntheticClass.methods().iterator().next();
-          for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
-            DexMethod memberReference = member.getMethod().getReference();
-            pruned.add(member.getHolder().getType());
-            if (memberReference != externalSyntheticMethod.method) {
-              lensBuilder.moveSyntheticMethod(memberReference, externalSyntheticMethod.method);
-            }
-          }
-        });
-
-    List<DexProgramClass> deduplicatedClasses = new ArrayList<>();
-    syntheticClassGroups.forEach(
-        (syntheticType, syntheticGroup) -> {
-          SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
-          SynthesizingContext context = representative.getContext();
-          context.registerPrefixRewriting(syntheticType, appView);
-          DexProgramClass externalSyntheticClass = representative.getHolder();
-          newProgramClasses.add(externalSyntheticClass);
-          addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
-          for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
-            DexProgramClass memberClass = member.getHolder();
-            DexType memberType = memberClass.getType();
-            pruned.add(memberType);
-            if (memberType != syntheticType) {
-              lensBuilder.moveSyntheticClass(memberType, syntheticType);
-            }
-            // The aliasing of the non-representative members needs to be recorded manually.
-            if (member != representative) {
-              deduplicatedClasses.add(memberClass);
-            }
-          }
-        });
-
-    for (DexProgramClass clazz : application.classes()) {
-      if (!pruned.contains(clazz.type)) {
-        newProgramClasses.add(clazz);
-      }
-    }
-    application = application.builder().replaceProgramClasses(newProgramClasses).build();
-
-    // We can only assert that the method container classes are in here as the classes need
-    // to be rewritten by the tree-fixer.
-    for (DexType key : syntheticMethodGroups.keySet()) {
-      assert application.definitionFor(key) != null;
-    }
-
-    DexApplication.Builder<?> builder = application.builder();
     TreeFixerBase treeFixer =
         new TreeFixerBase(appView) {
           @Override
           public DexType mapClassType(DexType type) {
-            return lensBuilder.syntheticClassesMap.getOrDefault(type, type);
+            return lensBuilder.typeMap.getOrDefault(type, type);
           }
 
           @Override
@@ -436,6 +345,59 @@
             lensBuilder.move(from, to);
           }
         };
+
+    List<DexProgramClass> deduplicatedClasses = new ArrayList<>();
+    syntheticMethodGroups.forEach(
+        (syntheticType, syntheticGroup) -> {
+          SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
+          SynthesizingContext context = representative.getContext();
+          context.registerPrefixRewriting(syntheticType, appView);
+          DexProgramClass representativeClass = representative.getHolder();
+          addSyntheticMarker(representative.getKind(), representativeClass, context, appView);
+          assert representativeClass.getMethodCollection().size() == 1;
+          for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
+            if (member != representative) {
+              pruned.add(member.getHolder().getType());
+              deduplicatedClasses.add(member.getHolder());
+            }
+            if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
+              derivedMainDexSynthetics.add(syntheticType);
+            }
+          }
+        });
+
+    syntheticClassGroups.forEach(
+        (syntheticType, syntheticGroup) -> {
+          SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
+          SynthesizingContext context = representative.getContext();
+          context.registerPrefixRewriting(syntheticType, appView);
+          addSyntheticMarker(
+              representative.getKind(), representative.getHolder(), context, appView);
+          for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
+            DexProgramClass memberClass = member.getHolder();
+            DexType memberType = memberClass.getType();
+            if (member != representative) {
+              pruned.add(memberType);
+              deduplicatedClasses.add(memberClass);
+            }
+            if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
+              derivedMainDexSynthetics.add(syntheticType);
+            }
+          }
+        });
+
+    for (DexProgramClass clazz : application.classes()) {
+      if (!pruned.contains(clazz.type)) {
+        newProgramClasses.add(clazz);
+      }
+    }
+    application = application.builder().replaceProgramClasses(newProgramClasses).build();
+
+    // Assert that the non-representatives have been removed from the app.
+    assert verifyNonRepresentativesRemovedFromApplication(application, syntheticClassGroups);
+    assert verifyNonRepresentativesRemovedFromApplication(application, syntheticMethodGroups);
+
+    DexApplication.Builder<?> builder = application.builder();
     treeFixer.fixupClasses(deduplicatedClasses);
     builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
     application = builder.build();
@@ -452,34 +414,21 @@
                   representative.getKind(),
                   representative.getContext(),
                   externalSyntheticClass.type));
-          for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
-            addMainDexAndSynthesizedFromForMember(
-                member,
-                externalSyntheticClass,
-                appView.appInfo().getMainDexInfo(),
-                appForLookup::programDefinitionFor);
-          }
         });
     syntheticMethodGroups.forEach(
         (syntheticType, syntheticGroup) -> {
           DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
           SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
+          assert externalSyntheticClass.getMethodCollection().size() == 1;
+          assert externalSyntheticClass.getMethodCollection().hasDirectMethods();
+          DexEncodedMethod syntheticMethodDefinition =
+              externalSyntheticClass.getMethodCollection().getDirectMethod(alwaysTrue());
           addFinalSyntheticMethod.accept(
               externalSyntheticClass,
               new SyntheticMethodReference(
                   representative.getKind(),
                   representative.getContext(),
-                  representative
-                      .getMethod()
-                      .getReference()
-                      .withHolder(externalSyntheticClass.type, factory)));
-          for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
-            addMainDexAndSynthesizedFromForMember(
-                member,
-                externalSyntheticClass,
-                appView.appInfo().getMainDexInfo(),
-                appForLookup::programDefinitionFor);
-          }
+                  syntheticMethodDefinition.getReference()));
         });
 
     for (DexType key : syntheticMethodGroups.keySet()) {
@@ -493,6 +442,18 @@
     return application;
   }
 
+  private static <T extends SyntheticDefinition<?, T, ?>>
+      boolean verifyNonRepresentativesRemovedFromApplication(
+          DexApplication application, Map<DexType, EquivalenceGroup<T>> syntheticGroups) {
+    for (EquivalenceGroup<?> syntheticGroup : syntheticGroups.values()) {
+      for (SyntheticDefinition<?, ?, ?> member : syntheticGroup.getMembers()) {
+        assert member == syntheticGroup.getRepresentative()
+            || application.definitionFor(member.getHolder().getType()) == null;
+      }
+    }
+    return true;
+  }
+
   private static void addSyntheticMarker(
       SyntheticKind kind,
       DexProgramClass externalSyntheticClass,
@@ -504,38 +465,6 @@
     }
   }
 
-  private static DexProgramClass createExternalMethodClass(
-      DexType syntheticType, SyntheticMethodDefinition representative, DexItemFactory factory) {
-    SyntheticProgramClassBuilder builder =
-        new SyntheticProgramClassBuilder(syntheticType, representative.getContext(), factory);
-    // TODO(b/158159959): Support grouping multiple methods per synthetic class.
-    builder.addMethod(
-        methodBuilder -> {
-          DexEncodedMethod definition = representative.getMethod().getDefinition();
-          methodBuilder
-              .setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)
-              .setAccessFlags(definition.accessFlags)
-              .setProto(definition.getProto())
-              .setClassFileVersion(
-                  definition.hasClassFileVersion() ? definition.getClassFileVersion() : null)
-              .setCode(m -> definition.getCode());
-        });
-    return builder.build();
-  }
-
-  private static void addMainDexAndSynthesizedFromForMember(
-      SyntheticDefinition<?, ?, ?> member,
-      DexProgramClass externalSyntheticClass,
-      MainDexInfo mainDexInfo,
-      Function<DexType, DexProgramClass> definitions) {
-    member.getContext().addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo);
-    // TODO(b/168584485): Remove this once class-mapping support is removed.
-    DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType());
-    if (from != null) {
-      externalSyntheticClass.addSynthesizedFrom(from);
-    }
-  }
-
   private static boolean shouldAnnotateSynthetics(InternalOptions options) {
     // Only intermediate builds have annotated synthetics to allow later sharing.
     // This is currently also disabled on CF to CF desugaring to avoid missing class references to
@@ -550,7 +479,8 @@
           Map<String, NumberGenerator> generators,
           AppView<?> appView,
           boolean intermediate,
-          ClassToFeatureSplitMap classToFeatureSplitMap) {
+          ClassToFeatureSplitMap classToFeatureSplitMap,
+          Builder lensBuilder) {
     Map<String, List<EquivalenceGroup<T>>> groupsPerPrefix = new HashMap<>();
     potentialEquivalences.forEach(
         members -> {
@@ -599,6 +529,9 @@
             DexType representativeType =
                 createExternalType(kind, externalSyntheticTypePrefix, generators, appView);
             equivalences.put(representativeType, group);
+            for (T member : group.getMembers()) {
+              lensBuilder.move(member.getHolder().getType(), representativeType);
+            }
           }
         });
     return equivalences;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 9922011..65c53d5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -47,7 +48,7 @@
      *
      * <p>TODO(b/158159959): Remove legacy support.
      */
-    private final Map<DexType, DexProgramClass> legacyClasses = new ConcurrentHashMap<>();
+    private final Map<DexType, LegacySyntheticDefinition> legacyClasses = new ConcurrentHashMap<>();
 
     /** Thread safe collection of synthetic items not yet committed to the application. */
     private final ConcurrentHashMap<DexType, SyntheticDefinition<?, ?, ?>> nonLegacyDefinitions =
@@ -75,7 +76,9 @@
           allPending.add(item.asProgramDefinition().getHolder());
         }
       }
-      allPending.addAll(legacyClasses.values());
+      for (LegacySyntheticDefinition legacy : legacyClasses.values()) {
+        allPending.add(legacy.getDefinition());
+      }
       return Collections.unmodifiableList(allPending);
     }
   }
@@ -146,17 +149,24 @@
 
   @Override
   public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
-    DexClass clazz = pending.legacyClasses.get(type);
-    if (clazz == null) {
+    DexClass clazz = null;
+    SyntheticKind kind = null;
+    LegacySyntheticDefinition legacyItem = pending.legacyClasses.get(type);
+    if (legacyItem != null) {
+      clazz = legacyItem.getDefinition();
+    } else {
       SyntheticDefinition<?, ?, ?> item = pending.nonLegacyDefinitions.get(type);
       if (item != null) {
         clazz = item.getHolder();
+        kind = item.getKind();
         assert clazz.isProgramClass() == item.isProgramDefinition();
         assert clazz.isClasspathClass() == item.isClasspathDefinition();
       }
     }
     if (clazz != null) {
+      assert legacyItem != null || kind != null;
       assert baseDefinitionFor.apply(type) == null
+              || (kind != null && kind.mayOverridesNonProgramType)
           : "Pending synthetic definition also present in the active program: " + type;
       return clazz;
     }
@@ -212,6 +222,22 @@
     return isCommittedSynthetic(type) || isPendingSynthetic(type);
   }
 
+  public SyntheticKind getNonLegacySyntheticKind(DexProgramClass clazz) {
+    assert isNonLegacySynthetic(clazz);
+    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(clazz.getType());
+    if (reference == null) {
+      SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
+      if (definition != null) {
+        reference = definition.toReference();
+      }
+    }
+    if (reference != null) {
+      return reference.getKind();
+    }
+    assert false;
+    return null;
+  }
+
   public boolean isSyntheticClass(DexType type) {
     return isLegacySyntheticClass(type) || isNonLegacySynthetic(type);
   }
@@ -229,6 +255,14 @@
     if (definition != null) {
       return Collections.singletonList(definition.getContext().getSynthesizingContextType());
     }
+    LegacySyntheticReference legacyReference = committed.getLegacyTypes().get(type);
+    if (legacyReference != null) {
+      return legacyReference.getContexts();
+    }
+    LegacySyntheticDefinition legacyDefinition = pending.legacyClasses.get(type);
+    if (legacyDefinition != null) {
+      return legacyDefinition.getContexts();
+    }
     return Collections.emptyList();
   }
 
@@ -266,7 +300,7 @@
   }
 
   public Collection<DexProgramClass> getLegacyPendingClasses() {
-    return Collections.unmodifiableCollection(pending.legacyClasses.values());
+    return ListUtils.map(pending.legacyClasses.values(), LegacySyntheticDefinition::getDefinition);
   }
 
   private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
@@ -285,12 +319,26 @@
 
   // Addition and creation of synthetic items.
 
+  public void addLegacySyntheticClassForLibraryDesugaring(DexProgramClass clazz) {
+    internalAddLegacySyntheticClass(clazz);
+    // No context information is added for library context.
+    // This is intended only to support desugared-library compilation.
+  }
+
   // TODO(b/158159959): Remove the usage of this direct class addition.
-  public void addLegacySyntheticClass(DexProgramClass clazz) {
+  public void addLegacySyntheticClass(DexProgramClass clazz, ProgramDefinition context) {
+    LegacySyntheticDefinition legacyItem = internalAddLegacySyntheticClass(clazz);
+    legacyItem.addContext(context);
+  }
+
+  private LegacySyntheticDefinition internalAddLegacySyntheticClass(DexProgramClass clazz) {
     assert !isCommittedSynthetic(clazz.type);
     assert !pending.nonLegacyDefinitions.containsKey(clazz.type);
-    DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz);
-    assert previous == null || previous == clazz;
+    LegacySyntheticDefinition legacyItem =
+        pending.legacyClasses.computeIfAbsent(
+            clazz.getType(), type -> new LegacySyntheticDefinition(clazz));
+    assert legacyItem.getDefinition() == clazz;
+    return legacyItem;
   }
 
   public DexProgramClass createClass(
@@ -342,6 +390,23 @@
     return clazz;
   }
 
+  public DexProgramClass createFixedClassFromType(
+      SyntheticKind kind,
+      DexType contextType,
+      DexItemFactory factory,
+      Consumer<SyntheticProgramClassBuilder> fn) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+    SyntheticProgramClassBuilder classBuilder =
+        new SyntheticProgramClassBuilder(type, outerContext, factory);
+    fn.accept(classBuilder);
+    DexProgramClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
   public DexClasspathClass createFixedClasspathClass(
       SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
     // Obtain the outer synthesizing context in the case the context itself is synthetic.
@@ -418,7 +483,7 @@
     CommittedSyntheticsCollection.Builder builder = committed.builder();
     // Legacy synthetics must already have been committed to the app.
     assert verifyClassesAreInApp(application, pending.legacyClasses.values());
-    builder.addLegacyClasses(pending.legacyClasses.values());
+    builder.addLegacyClasses(pending.legacyClasses);
     // Compute the synthetic additions and add them to the application.
     ImmutableList<DexType> committedProgramTypes;
     DexApplication amendedApplication;
@@ -432,7 +497,12 @@
         if (!removedClasses.contains(definition.getHolder().getType())) {
           if (definition.isProgramDefinition()) {
             committedProgramTypesBuilder.add(definition.getHolder().getType());
-            appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
+            if (definition.getKind().mayOverridesNonProgramType) {
+              appBuilder.addProgramClassPotentiallyOverridingNonProgramClass(
+                  definition.asProgramDefinition().getHolder());
+            } else {
+              appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
+            }
           } else if (appBuilder.isDirect()) {
             assert definition.isClasspathDefinition();
             appBuilder.asDirect().addClasspathClass(definition.asClasspathDefinition().getHolder());
@@ -451,8 +521,9 @@
   }
 
   private static boolean verifyClassesAreInApp(
-      DexApplication app, Collection<DexProgramClass> classes) {
-    for (DexProgramClass clazz : classes) {
+      DexApplication app, Collection<LegacySyntheticDefinition> classes) {
+    for (LegacySyntheticDefinition item : classes) {
+      DexProgramClass clazz = item.getDefinition();
       assert app.programDefinitionFor(clazz.type) != null : "Missing synthetic: " + clazz.type;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index dfc240b..9c19e81 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -20,7 +20,7 @@
  */
 class SyntheticMethodReference
     extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
-    implements SyntheticProgramReference {
+    implements SyntheticProgramReference, Rewritable<SyntheticMethodReference> {
   final DexMethod method;
 
   SyntheticMethodReference(SyntheticKind kind, SynthesizingContext context, DexMethod method) {
@@ -29,7 +29,7 @@
   }
 
   @Override
-  DexType getHolder() {
+  public DexType getHolder() {
     return method.holder;
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 1b80f41..7148738 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -23,9 +23,13 @@
    */
   public enum SyntheticKind {
     // Class synthetics.
+    RECORD_TAG("", false, true, true),
     COMPANION_CLASS("CompanionClass", false),
     LAMBDA("Lambda", false),
     INIT_TYPE_ARGUMENT("-IA", false, true),
+    HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", false, true),
+    HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", false, true),
+    HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", false, true),
     // Method synthetics.
     BACKPORT("Backport", true),
     STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
@@ -41,6 +45,7 @@
     public final String descriptor;
     public final boolean isSingleSyntheticMethod;
     public final boolean isFixedSuffixSynthetic;
+    public final boolean mayOverridesNonProgramType;
 
     SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) {
       this(descriptor, isSingleSyntheticMethod, false);
@@ -48,9 +53,22 @@
 
     SyntheticKind(
         String descriptor, boolean isSingleSyntheticMethod, boolean isFixedSuffixSynthetic) {
+      this(descriptor, isSingleSyntheticMethod, isFixedSuffixSynthetic, false);
+    }
+
+    SyntheticKind(
+        String descriptor,
+        boolean isSingleSyntheticMethod,
+        boolean isFixedSuffixSynthetic,
+        boolean mayOverridesNonProgramType) {
       this.descriptor = descriptor;
       this.isSingleSyntheticMethod = isSingleSyntheticMethod;
       this.isFixedSuffixSynthetic = isFixedSuffixSynthetic;
+      this.mayOverridesNonProgramType = mayOverridesNonProgramType;
+    }
+
+    public boolean allowSyntheticContext() {
+      return this == RECORD_TAG;
     }
 
     public static SyntheticKind fromDescriptor(String descriptor) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
index 5a17b04..d5973f6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -19,7 +19,7 @@
 class SyntheticProgramClassReference
     extends SyntheticClassReference<
         SyntheticProgramClassReference, SyntheticProgramClassDefinition, DexProgramClass>
-    implements SyntheticProgramReference {
+    implements SyntheticProgramReference, Rewritable<SyntheticProgramClassReference> {
 
   SyntheticProgramClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) {
     super(kind, context, type);
@@ -42,9 +42,6 @@
     // If the reference has been non-trivially rewritten the compiler has changed it and it can no
     // longer be considered a synthetic. The context may or may not have changed.
     if (type != rewritten && !lens.isSimpleRenaming(type, rewritten)) {
-      // If the referenced item is rewritten, it should be moved to another holder as the
-      // synthetic holder is no longer part of the synthetic collection.
-      assert SyntheticNaming.verifyNotInternalSynthetic(rewritten);
       return null;
     }
     if (rewrittenContext == getContext() && rewritten == type) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index ddb28f7..9824ebc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -44,7 +44,7 @@
 
   abstract DexReference getReference();
 
-  final R rewrite(NonIdentityGraphLens lens) {
+  public final R rewrite(NonIdentityGraphLens lens) {
     SynthesizingContext rewrittenContext = getContext().rewrite(lens);
     return internalRewrite(rewrittenContext, lens);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 1534b4a..d1f6e4c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -89,6 +89,7 @@
   private static final String dumpBuildPropertiesFileName = "build.properties";
   private static final String dumpDesugaredLibraryFileName = "desugared-library.json";
   private static final String dumpMainDexListResourceFileName = "main-dex-list.txt";
+  private static final String dumpMainDexRulesResourceFileName = "main-dex-rules.txt";
   private static final String dumpProgramFileName = "program.jar";
   private static final String dumpClasspathFileName = "classpath.jar";
   private static final String dumpLibraryFileName = "library.jar";
@@ -512,6 +513,13 @@
         String join = StringUtils.join(mainDexList, "\n");
         writeToZipStream(out, dumpMainDexListResourceFileName, join.getBytes(), ZipEntry.DEFLATED);
       }
+      if (options.hasMainDexKeepRules()) {
+        writeToZipStream(
+            out,
+            dumpMainDexRulesResourceFileName,
+            StringUtils.joinLines(options.getMainDexKeepRules()).getBytes(),
+            ZipEntry.DEFLATED);
+      }
       nextDexIndex =
           dumpProgramResources(
               dumpProgramFileName,
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index b7839ea..5a4ee11 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -13,6 +13,15 @@
 
 public class ArrayUtils {
 
+  public static boolean containsInt(int[] array, int value) {
+    for (int element : array) {
+      if (element == value) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Copies the input array and then applies specified sparse changes.
    *
@@ -71,6 +80,13 @@
         clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
   }
 
+  public static boolean isSorted(int[] array) {
+    for (int i = 0; i < array.length - 1; i++) {
+      assert array[i] < array[i + 1];
+    }
+    return true;
+  }
+
   /**
    * Rewrites the input array based on the given function.
    *
diff --git a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
index f470d35..5245876 100644
--- a/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
@@ -15,4 +15,16 @@
   public static <S, T> BiPredicate<S, T> alwaysTrue() {
     return (s, t) -> true;
   }
+
+  @SafeVarargs
+  public static <S, T> BiPredicate<S, T> or(BiPredicate<S, T>... predicates) {
+    return (s, t) -> {
+      for (BiPredicate<S, T> predicate : predicates) {
+        if (predicate.test(s, t)) {
+          return true;
+        }
+      }
+      return false;
+    };
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 3c7b99b..c885d9e 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -115,6 +116,21 @@
   }
 
   /**
+   * Clears the type so if a class with the given type was present, it cannot be found anymore. This
+   * has to be run at a join point, concurrent accesses may be confused.
+   */
+  public void clearType(DexType type) {
+    if (classes.containsKey(type)) {
+      classes.remove(type);
+    }
+    ClassProvider<T> provider = classProvider.get();
+    if (provider == null) {
+      return;
+    }
+    classProvider.set(provider.without(ImmutableSet.of(type)));
+  }
+
+  /**
    * Returns all classes from the collection. The collection must be force-loaded.
    */
   public List<T> getAllClasses() {
diff --git a/src/main/java/com/android/tools/r8/utils/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
index c048529..af272b8 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -70,6 +71,10 @@
     return new PreloadedClassProvider<>(classKind, builder.build());
   }
 
+  public FilteringClassProvider<T> without(Set<DexType> filteredTypes) {
+    return new FilteringClassProvider(classKind, this, filteredTypes);
+  }
+
   /** Create class provider for preloaded classes. */
   public static <T extends DexClass> ClassProvider<T> combine(
       ClassKind<T> classKind, List<ClassProvider<T>> providers) {
@@ -145,6 +150,47 @@
     }
   }
 
+  /** Class provider which ignores a list of filtered classes */
+  private static class FilteringClassProvider<T extends DexClass> extends ClassProvider<T> {
+    private final ClassProvider<T> provider;
+    private final Set<DexType> filteredOut;
+
+    FilteringClassProvider(
+        ClassKind<T> classKind, ClassProvider<T> provider, Set<DexType> filteredOut) {
+      super(classKind);
+      assert !(provider instanceof FilteringClassProvider) : "Nested Filtering class providers";
+      this.provider = provider;
+      this.filteredOut = filteredOut;
+    }
+
+    @Override
+    public FilteringClassProvider<T> without(Set<DexType> filteredTypes) {
+      ImmutableSet<DexType> newSet =
+          ImmutableSet.<DexType>builder().addAll(filteredOut).addAll(filteredTypes).build();
+      return new FilteringClassProvider(getClassKind(), provider, newSet);
+    }
+
+    @Override
+    public void collectClass(DexType type, Consumer<T> classConsumer) {
+      if (filteredOut.contains(type)) {
+        return;
+      }
+      provider.collectClass(type, classConsumer);
+    }
+
+    @Override
+    public Collection<DexType> collectTypes() {
+      Collection<DexType> dexTypes = provider.collectTypes();
+      dexTypes.removeAll(filteredOut);
+      return dexTypes;
+    }
+
+    @Override
+    public String toString() {
+      return provider + " without " + filteredOut;
+    }
+  }
+
   private static class CombinedClassProvider<T extends DexClass> extends ClassProvider<T> {
     private final List<ClassProvider<T>> providers;
 
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index a32e296..292f103 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -75,6 +75,7 @@
     List<Path> library = new ArrayList<>();
     List<Path> classpath = new ArrayList<>();
     List<Path> config = new ArrayList<>();
+    List<Path> mainDexRulesFiles = new ArrayList<>();
     int minApi = 1;
     int threads = -1;
     for (int i = 0; i < args.length; i++) {
@@ -147,6 +148,11 @@
               threads = Integer.parseInt(operand);
               break;
             }
+          case "--main-dex-rules":
+            {
+              mainDexRulesFiles.add(Paths.get(operand));
+              break;
+            }
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
@@ -178,6 +184,7 @@
             .addLibraryFiles(library)
             .addClasspathFiles(classpath)
             .addProguardConfigurationFiles(config)
+            .addMainDexRulesFiles(mainDexRulesFiles)
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode);
     if (desugaredLibJson != null) {
diff --git a/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java b/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java
new file mode 100644
index 0000000..7da945b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/Int2ObjectMapUtils.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+
+public class Int2ObjectMapUtils {
+
+  public static <V> void forEach(Int2ObjectMap<V> map, IntObjConsumer<V> consumer) {
+    for (Int2ObjectMap.Entry<V> entry : map.int2ObjectEntrySet()) {
+      consumer.accept(entry.getIntKey(), entry.getValue());
+    }
+  }
+
+  public static <V> V getOrDefault(Int2ObjectMap<V> map, int key, V defaultValue) {
+    V value = map.get(key);
+    return value != null ? value : defaultValue;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java b/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java
new file mode 100644
index 0000000..a73a7be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IntObjPredicate.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+public interface IntObjPredicate<T> {
+
+  boolean test(int i, T obj);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
new file mode 100644
index 0000000..9c07bed
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IntObjToObjFunction.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+public interface IntObjToObjFunction<S, T> {
+
+  S apply(int i, T obj);
+}
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 ea310aa..54bbbcc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -490,9 +490,16 @@
     return !canUseNestBasedAccess();
   }
 
-  public boolean canUseRecords() {
-    // TODO(b/169645628): Replace by true when records are supported.
-    return testing.canUseRecords;
+  public boolean enableExperimentalRecordDesugaring() {
+    // TODO(b/169645628): Remove when records are supported.
+    return testing.enableExperimentalRecordDesugaring;
+  }
+
+  public boolean shouldDesugarRecords() {
+    if (!enableExperimentalRecordDesugaring()) {
+      return false;
+    }
+    return desugarState.isOn() && !canUseRecords();
   }
 
   public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
@@ -1032,7 +1039,7 @@
     }
     // Currently the filter is simple string equality on the qualified name.
     String qualifiedName = method.qualifiedName();
-    return methodsFilter.indexOf(qualifiedName) >= 0;
+    return methodsFilter.contains(qualifiedName);
   }
 
   public boolean methodMatchesLogArgumentsFilter(DexEncodedMethod method) {
@@ -1042,7 +1049,7 @@
     }
     // Currently the filter is simple string equality on the qualified name.
     String qualifiedName = method.qualifiedName();
-    return logArgumentsFilter.indexOf(qualifiedName) >= 0;
+    return logArgumentsFilter.contains(qualifiedName);
   }
 
   public enum PackageObfuscationMode {
@@ -1151,15 +1158,10 @@
 
     public boolean enable = true;
     public boolean enableConstructorMerging = true;
-    // TODO(b/174809311): Update or remove the option and its tests after new lambdas synthetics.
-    public boolean enableJavaLambdaMerging = false;
+    public boolean enableJavaLambdaMerging = true;
 
-    public int syntheticArgumentCount = 3;
     public int maxGroupSize = 30;
 
-    // TODO(b/179019716): Add support for merging in presence of annotations.
-    public boolean skipNoClassesOrMembersWithAnnotationsPolicyForTesting = false;
-
     public void disable() {
       enable = false;
     }
@@ -1180,10 +1182,6 @@
       return maxGroupSize;
     }
 
-    public int getSyntheticArgumentCount() {
-      return syntheticArgumentCount;
-    }
-
     public boolean isConstructorMergingEnabled() {
       return enableConstructorMerging;
     }
@@ -1295,7 +1293,7 @@
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
-    public boolean canUseRecords = false;
+    public boolean enableExperimentalRecordDesugaring = false;
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
@@ -1456,6 +1454,10 @@
     return !isDesugaring();
   }
 
+  public boolean canUseRecords() {
+    return !isDesugaring();
+  }
+
   public boolean canLeaveStaticInterfaceMethodInvokes() {
     return !isDesugaring() || hasMinApi(AndroidApiLevel.L);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 93b7e4a..3ecdc4f 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.google.common.collect.Iterables;
 import java.util.Iterator;
 import java.util.ListIterator;
 import java.util.NoSuchElementException;
@@ -16,6 +17,27 @@
 
 public class IteratorUtils {
 
+  public static <T> Iterator<T> createCircularIterator(Iterable<T> iterable) {
+    assert !Iterables.isEmpty(iterable);
+    return new Iterator<T>() {
+
+      private Iterator<T> iterator = iterable.iterator();
+
+      @Override
+      public boolean hasNext() {
+        return true;
+      }
+
+      @Override
+      public T next() {
+        if (!iterator.hasNext()) {
+          iterator = iterable.iterator();
+        }
+        return iterator.next();
+      }
+    };
+  }
+
   public static <T> int countRemaining(Iterator<T> iterator) {
     IntBox counter = new IntBox();
     iterator.forEachRemaining(ignore -> counter.increment());
diff --git a/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java b/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java
new file mode 100644
index 0000000..48a1e90
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/JoiningStringConsumer.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringConsumer.ForwardingConsumer;
+
+/* Joining String Consumer to join strings that it accepts. */
+public class JoiningStringConsumer extends ForwardingConsumer {
+
+  private final String separator;
+  private final StringConsumer consumer;
+  private final StringBuilder builder = new StringBuilder();
+
+  /**
+   * @param consumer Consumer to forward to the joined input to. If null, nothing will be forwarded.
+   */
+  public JoiningStringConsumer(StringConsumer consumer, String separator) {
+    super(consumer);
+    this.consumer = consumer;
+    this.separator = separator;
+  }
+
+  @Override
+  public void accept(String string, DiagnosticsHandler handler) {
+    if (builder.length() > 0) {
+      builder.append(separator);
+    }
+    builder.append(string);
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {
+    super.accept(builder.toString(), handler);
+    super.finished(handler);
+  }
+
+  public StringConsumer getConsumer() {
+    return consumer;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java b/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java
new file mode 100644
index 0000000..8c65b00
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SortingStringConsumer.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringConsumer.ForwardingConsumer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Sorting consumer that accepts all input and then sorts it when calling finished */
+public class SortingStringConsumer extends ForwardingConsumer {
+
+  private final List<String> accepted = new ArrayList<>();
+
+  /**
+   * @param consumer Consumer to forward to the sorted consumed input to. If null, nothing will be
+   *     forwarded.
+   */
+  public SortingStringConsumer(StringConsumer consumer) {
+    super(consumer);
+  }
+
+  @Override
+  public void accept(String string, DiagnosticsHandler handler) {
+    this.accepted.add(string);
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {
+    Collections.sort(accepted);
+    accepted.forEach(string -> super.accept(string, handler));
+    super.finished(handler);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
index 8f29df9..b75d224 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
@@ -130,10 +130,11 @@
   }
 
   @Override
-  public void put(K key, V value) {
-    remove(key);
+  public V put(K key, V value) {
+    V old = remove(key);
     backing.put(key, value);
     inverse.computeIfAbsent(value, ignore -> new LinkedHashSet<>()).add(key);
+    return old;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
index 830138a..e23a565 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
@@ -77,6 +77,11 @@
   }
 
   @Override
+  public K getKey(V value) {
+    return backing.inverse().get(value);
+  }
+
+  @Override
   public BiMap<K, V> getForwardMap() {
     return backing;
   }
@@ -88,12 +93,12 @@
 
   @Override
   public K getRepresentativeKey(V value) {
-    return backing.inverse().get(value);
+    return getKey(value);
   }
 
   @Override
   public V getRepresentativeValue(K key) {
-    return backing.get(key);
+    return get(key);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java
index e5085c2..569c52e 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneMap.java
@@ -15,6 +15,8 @@
 public interface BidirectionalOneToOneMap<K, V>
     extends BidirectionalManyToOneRepresentativeMap<K, V> {
 
+  K getKey(V value);
+
   @Override
   BiMap<K, V> getForwardMap();
 
diff --git a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
index f91f7df..62fba9a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
@@ -57,6 +57,11 @@
   }
 
   @Override
+  public K getKey(V value) {
+    return null;
+  }
+
+  @Override
   public BiMap<K, V> getForwardMap() {
     return HashBiMap.create();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java
index 7953e91..05bd5ea 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneMap.java
@@ -11,7 +11,7 @@
 
   void clear();
 
-  void put(K key, V value);
+  V put(K key, V value);
 
   void put(Iterable<K> key, V value);
 
diff --git a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java
index 5e5f469..87a29fa 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalOneToOneMap.java
@@ -10,4 +10,6 @@
   V put(K key, V value);
 
   void putAll(BidirectionalManyToManyMap<K, V> map);
+
+  V remove(Object key);
 }
diff --git a/src/test/examples/classmerging/ProguardFieldMapTest.java b/src/test/examples/classmerging/ProguardFieldMapTest.java
index e512173..98ca7b2 100644
--- a/src/test/examples/classmerging/ProguardFieldMapTest.java
+++ b/src/test/examples/classmerging/ProguardFieldMapTest.java
@@ -13,7 +13,7 @@
 
   public static class A {
 
-    public String f = "A.f";
+    public String f = System.currentTimeMillis() > 0 ? "A.f" : null;
   }
 
   @NeverClassInline
diff --git a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
index 627d255..25c0af0 100644
--- a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
+++ b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
@@ -471,15 +471,6 @@
     static final InstanceAndClassChecks INSTANCE = new InstanceAndClassChecks();
 
     static void test() {
-      assertSameInstance(
-          InstanceAndClassChecks::staticProvider,
-          InstanceAndClassChecks::staticProvider,
-          "Instances must be same");
-      assertSameInstance(
-          InstanceAndClassChecks::staticProvider,
-          statelessLambda(),
-          "Instances must be same");
-
       assertDifferentInstance(
           INSTANCE::instanceProvider,
           INSTANCE::instanceProvider,
diff --git a/src/test/examplesJava9/backport/IntegerBackportJava9Main.java b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
new file mode 100644
index 0000000..30e9086
--- /dev/null
+++ b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package backport;
+
+import java.math.BigInteger;
+
+public final class IntegerBackportJava9Main {
+  private static final int[] interestingValues = {
+    Integer.MIN_VALUE,
+    Integer.MAX_VALUE,
+    Short.MIN_VALUE,
+    Short.MAX_VALUE,
+    Byte.MIN_VALUE,
+    Byte.MAX_VALUE,
+    0,
+    -1,
+    1,
+    -42,
+    42
+  };
+
+  public static void main(String[] args) {
+    testParseIntegerSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+  }
+
+  private static void testParseIntegerSubsequenceWithRadix(boolean supportsPlusPrefix) {
+    for (int value : interestingValues) {
+      for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+        for (String prefix : new String[] {"", "x", "xxx"}) {
+          for (String postfix : new String[] {"", "x", "xxx"}) {
+            String valueString = prefix + Long.toString(value, radix) + postfix;
+            int start = prefix.length();
+            int end = valueString.length() - postfix.length();
+            assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
+            if (value > 0 && supportsPlusPrefix) {
+              valueString = prefix + '+' + Long.toString(value, radix) + postfix;
+              end++;
+              assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
+            }
+          }
+        }
+      }
+    }
+
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+    } catch (IllegalArgumentException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+    } catch (NumberFormatException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+
+    BigInteger overflow = new BigInteger("18446744073709551616");
+    for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+      for (String prefix : new String[] {"", "x", "xxx"}) {
+        for (String postfix : new String[] {"", "x", "xxx"}) {
+          String overflowString = prefix + overflow.toString(radix) + postfix;
+          int start = prefix.length();
+          int end = overflowString.length() - postfix.length();
+          try {
+            throw new AssertionError(Long.parseLong(overflowString, start, end, radix));
+          } catch (NumberFormatException expected) {
+          }
+          String underflowString = prefix + '-' + overflow.toString(radix) + postfix;
+          start = prefix.length();
+          end = underflowString.length() - postfix.length();
+          try {
+            throw new AssertionError(Long.parseLong(underflowString, start, end, radix));
+          } catch (NumberFormatException expected) {
+          }
+        }
+      }
+    }
+  }
+
+  private static void assertEquals(String m, int expected, int actual) {
+    if (expected != actual) {
+      throw new AssertionError(m + " Expected <" + expected + "> but was <" + actual + '>');
+    }
+  }
+}
diff --git a/src/test/examplesJava9/backport/LongBackportJava9Main.java b/src/test/examplesJava9/backport/LongBackportJava9Main.java
new file mode 100644
index 0000000..1634a43
--- /dev/null
+++ b/src/test/examplesJava9/backport/LongBackportJava9Main.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package backport;
+
+import java.math.BigInteger;
+
+public final class LongBackportJava9Main {
+  private static final long[] interestingValues = {
+    Long.MIN_VALUE,
+    Long.MAX_VALUE,
+    Integer.MIN_VALUE,
+    Integer.MAX_VALUE,
+    Short.MIN_VALUE,
+    Short.MAX_VALUE,
+    Byte.MIN_VALUE,
+    Byte.MAX_VALUE,
+    0L,
+    -1L,
+    1L,
+    -42L,
+    42L
+  };
+
+  public static void main(String[] args) {
+    testParseLongSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+    testParseUnsignedLongSubsequenceWithRadix();
+  }
+
+  private static void testParseLongSubsequenceWithRadix(boolean supportsPlusPrefix) {
+    for (long value : interestingValues) {
+      for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+        for (String prefix : new String[] {"", "x", "xxx"}) {
+          for (String postfix : new String[] {"", "x", "xxx"}) {
+            String valueString = prefix + Long.toString(value, radix) + postfix;
+            int start = prefix.length();
+            int end = valueString.length() - postfix.length();
+            assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
+            if (value > 0 && supportsPlusPrefix) {
+              valueString = prefix + "+" + Long.toString(value, radix) + postfix;
+              end++;
+              assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
+            }
+          }
+        }
+      }
+    }
+
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+    } catch (IllegalArgumentException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+    } catch (NumberFormatException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+
+    BigInteger overflow = new BigInteger("18446744073709551616");
+    for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+      for (String prefix : new String[] {"", "x", "xxx"}) {
+        for (String postfix : new String[] {"", "x", "xxx"}) {
+          String overflowString = prefix + overflow.toString(radix) + postfix;
+          int start = prefix.length();
+          int end = overflowString.length() - postfix.length();
+          try {
+            throw new AssertionError(Long.parseLong(overflowString, start, end, radix));
+          } catch (NumberFormatException expected) {
+          }
+        }
+      }
+    }
+  }
+
+  private static void testParseUnsignedLongSubsequenceWithRadix() {
+    for (long value : interestingValues) {
+      for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+        for (String prefix : new String[] {"", "x", "xxx"}) {
+          for (String postfix : new String[] {"", "x", "xxx"}) {
+            String valueString = prefix + unsignedLongToBigInteger(value).toString(radix) + postfix;
+            int start = prefix.length();
+            int end = valueString.length() - postfix.length();
+            assertEquals(
+                valueString, value, Long.parseUnsignedLong(valueString, start, end, radix));
+            valueString = prefix + "+" + unsignedLongToBigInteger(value).toString(radix) + postfix;
+            end++;
+            assertEquals(
+                valueString, value, Long.parseUnsignedLong(valueString, start, end, radix));
+          }
+        }
+      }
+    }
+
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+    } catch (IllegalArgumentException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+    } catch (IllegalArgumentException expected) {
+    }
+
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+    } catch (NumberFormatException expected) {
+    }
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+
+    BigInteger overflow = new BigInteger("18446744073709551616");
+    for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
+      for (String prefix : new String[] {"", "x", "xxx", "+", "x+", "xxx+"}) {
+        for (String postfix : new String[] {"", "x", "xxx"}) {
+          String overflowString = prefix + overflow.toString(radix) + postfix;
+          int start = prefix.length();
+          int end = overflowString.length() - postfix.length();
+          try {
+            throw new AssertionError(Long.parseUnsignedLong(overflowString, start, end, radix));
+          } catch (NumberFormatException expected) {
+          }
+        }
+      }
+    }
+  }
+
+  private static BigInteger unsignedLongToBigInteger(long value) {
+    BigInteger bigInt = BigInteger.valueOf(value & 0x7fffffffffffffffL);
+    if (value < 0) {
+      bigInt = bigInt.setBit(Long.SIZE - 1);
+    }
+    return bigInt;
+  }
+
+  private static void assertEquals(String m, long expected, long actual) {
+    if (expected != actual) {
+      throw new AssertionError(m + " Expected <" + expected + "> but was <" + actual + '>');
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 43bbd99..c5ce290 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -362,11 +362,7 @@
 
   @Override
   protected void testIntermediateWithMainDexList(
-      String packageName,
-      Path input,
-      int expectedMainDexListSize,
-      List<String> mainDexClasses,
-      List<String> mainDexOverApproximation)
+      String packageName, Path input, int expectedMainDexListSize, List<String> mainDexClasses)
       throws Throwable {
     // Skip those tests.
     Assume.assumeTrue(false);
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 4913bc5..67eeeb0 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -99,4 +99,11 @@
     }
     return self();
   }
+
+  public D8TestBuilder addMainDexKeepClassAndMemberRules(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      addMainDexRules("-keep class " + clazz.getTypeName() + " { *; }");
+    }
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index e169df0..488480c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -107,14 +108,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
         .run();
   }
 
@@ -146,14 +147,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
         .run();
   }
 
@@ -167,7 +168,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 37, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -176,7 +177,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -190,7 +191,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 36, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -199,19 +200,19 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
         .run();
   }
 
   private void checkLambdaCount(CodeInspector inspector, int maxExpectedCount, String prefix) {
-    int count = 0;
+    List<String> found = new ArrayList<>();
     for (FoundClassSubject clazz : inspector.allClasses()) {
       if (clazz.isSynthesizedJavaLambdaClass() &&
           clazz.getOriginalName().startsWith(prefix)) {
-        count++;
+        found.add(clazz.getOriginalName());
       }
     }
-    assertEquals(maxExpectedCount, count);
+    assertEquals(StringUtils.lines(found), maxExpectedCount, found.size());
   }
 
   private void checkTestMultipleInterfacesCheckCastCount(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 52480be..bd9bd27 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -10,13 +10,13 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OffOrAuto;
@@ -115,21 +115,19 @@
       return self();
     }
 
-    C withMainDexClass(String... classes) {
-      return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
-    }
-
     C withMainDexKeepClassRules(List<String> classes) {
       return withBuilderTransformation(
           builder -> {
             if (builder instanceof D8Command.Builder) {
               ((D8Command.Builder) builder)
                   .addMainDexRules(
-                      ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown());
+                      ListUtils.map(classes, c -> "-keep class " + c + " { *; }"),
+                      Origin.unknown());
             } else if (builder instanceof R8Command.Builder) {
               ((R8Command.Builder) builder)
                   .addMainDexRules(
-                      ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown());
+                      ListUtils.map(classes, c -> "-keep class " + c + " { *; }"),
+                      Origin.unknown());
             } else {
               fail("Unexpected builder type: " + builder.getClass());
             }
@@ -486,22 +484,14 @@
   public void testLambdaDesugaringWithMainDexList1() throws Throwable {
     // Minimal case: there are synthesized classes but not form the main dex class.
     testIntermediateWithMainDexList(
-        "lambdadesugaring",
-        1,
-        ImmutableList.of("lambdadesugaring.LambdaDesugaring$I"),
-        ImmutableList.of());
+        "lambdadesugaring", 1, ImmutableList.of("lambdadesugaring.LambdaDesugaring$I"));
   }
 
   @Test
   public void testLambdaDesugaringWithMainDexList2() throws Throwable {
     // Main dex class has many lambdas.
     testIntermediateWithMainDexList(
-        "lambdadesugaring",
-        // TODO(b/180074885): Over approximation not present in R8.
-        this instanceof R8RunExamplesAndroidOTest ? 51 : 52,
-        ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B"),
-        // TODO(b/180074885): Over approximation due to invoke-dynamic reference adds as dependency.
-        ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$D"));
+        "lambdadesugaring", 98, ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B"));
   }
 
   @Test
@@ -511,11 +501,7 @@
         "interfacemethods",
         Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION),
         2,
-        ImmutableList.of("interfacemethods.I1"),
-        // TODO(b/180074885): Over approximation due to including I1-CC by being derived from I1,
-        //  but after desugaring I1 does not reference I1$-CC (the static method is moved), so it
-        //  is incorrect to include I1-CC in the main dex.
-        ImmutableList.of("interfacemethods.I1$-CC"));
+        ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC"));
   }
 
 
@@ -526,11 +512,7 @@
         "interfacemethods",
         Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION),
         2,
-        ImmutableList.of("interfacemethods.I2"),
-        // TODO(b/180074885): Over approximation due to including I2$-CC by being derived from I2,
-        //  but after desugaring I2 does not reference I2$-CC (the default method is moved), so it
-        //  is incorrect to include I2$-CC in the main dex.
-        ImmutableList.of("interfacemethods.I2$-CC"));
+        ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC"));
   }
 
   @Test
@@ -544,26 +526,21 @@
   }
 
   private void testIntermediateWithMainDexList(
-      String packageName,
-      int expectedMainDexListSize,
-      List<String> mainDexClasses,
-      List<String> mainDexOverApproximation)
+      String packageName, int expectedMainDexListSize, List<String> mainDexClasses)
       throws Throwable {
     testIntermediateWithMainDexList(
         packageName,
         Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION),
         expectedMainDexListSize,
-        mainDexClasses,
-        mainDexOverApproximation);
+        mainDexClasses);
   }
 
   protected void testIntermediateWithMainDexList(
-      String packageName,
-      Path input,
-      int expectedMainDexListSize,
-      List<String> mainDexClasses,
-      List<String> mainDexOverApproximation)
+      String packageName, Path input, int expectedMainDexListSize, List<String> mainDexClasses)
       throws Throwable {
+    // R8 does not support merging intermediate builds via DEX.
+    assumeFalse(this instanceof R8RunExamplesAndroidOTest);
+
     AndroidApiLevel minApi = AndroidApiLevel.K;
 
     // Full build, will be used as reference.
@@ -604,13 +581,8 @@
 
     // Check.
     Assert.assertEquals(expectedMainDexListSize, fullMainClasses.size());
-    SetView<String> adjustedFull =
-        Sets.difference(
-            fullMainClasses,
-            new HashSet<>(
-                ListUtils.map(mainDexOverApproximation, DescriptorUtils::javaTypeToDescriptor)));
-    assertEqualSets(adjustedFull, indexedIntermediateMainClasses);
-    assertEqualSets(adjustedFull, filePerInputClassIntermediateMainClasses);
+    assertEqualSets(fullMainClasses, indexedIntermediateMainClasses);
+    assertEqualSets(fullMainClasses, filePerInputClassIntermediateMainClasses);
   }
 
   <T> void assertEqualSets(Set<T> expected, Set<T> actual) {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 26477d3..e9cd5ff 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -285,13 +285,6 @@
     return setMinApi(minApi);
   }
 
-  public T setMinApiThreshold(TestRuntime runtime) {
-    if (runtime.isDex()) {
-      setMinApiThreshold(runtime.asDex().getMinApiLevel());
-    }
-    return self();
-  }
-
   public T setMinApi(AndroidApiLevel minApiLevel) {
     return setMinApi(minApiLevel.getLevel());
   }
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java
new file mode 100644
index 0000000..ae574e9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidLongStackValueMaxHeightTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+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 InvalidLongStackValueMaxHeightTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"52"};
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InvalidLongStackValueMaxHeightTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, Tester.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8Cf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Tester.class)
+        .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningThatMatches(
+                  diagnosticMessage(containsString("The max stack height of 2 is violated")));
+            });
+  }
+
+  @Test()
+  public void testD8Dex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Tester.class)
+        .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningThatMatches(
+                  diagnosticMessage(containsString("The max stack height of 2 is violated")));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public byte[] getMainWithChangedMaxStackHeight() throws Exception {
+    return transformer(Main.class).setMaxStackHeight(MethodPredicate.onName("main"), 2).transform();
+  }
+
+  public static class Tester {
+
+    public static void test(long x, int y) {
+      System.out.println(x + y);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Tester.test(10L, 42);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
new file mode 100644
index 0000000..71b6637
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/InvalidStackHeightTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+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 InvalidStackHeightTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"42"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InvalidStackHeightTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8Cf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The max stack height of 1 is violated"));
+            });
+  }
+
+  @Test
+  public void testD8Dex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The max stack height of 1 is violated"));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test()
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getMainWithChangedMaxStackHeight())
+        .enableInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningsMatch(
+                  diagnosticMessage(containsString("The max stack height of 1 is violated")));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public byte[] getMainWithChangedMaxStackHeight() throws Exception {
+    return transformer(Main.class).setMaxStackHeight(MethodPredicate.onName("main"), 1).transform();
+  }
+
+  public static class Main {
+
+    @NeverInline
+    private void test(int x, int y) {
+      System.out.println(x + y);
+    }
+
+    public static void main(String[] args) {
+      new Main().test(args.length, 42);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java
new file mode 100644
index 0000000..742472b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.LongStackValuesInFramesTest.LongStackValuesInFramesTest$MainDump.dump;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class LongStackValuesInFramesTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"52"};
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public LongStackValuesInFramesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(Tester.class)
+        .addProgramClassFileData(dump())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Tester.class)
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class Tester {
+
+    @NeverInline
+    public static void test(long x, int y) {
+      System.out.println(x + y);
+    }
+  }
+
+  public static class Main {
+
+    // This code will be rewritten to:
+    // ldc_w 10
+    // bipush 10
+    // if (args.length == 0) {
+    //   invoke Tester.test(JI)V
+    // }
+    // pop
+    // pop2
+    public static void main(String[] args) {
+      long x = 10L;
+      int y = 42;
+      if (args.length == 0) {
+        Tester.test(x, y);
+      }
+    }
+  }
+
+  public static class LongStackValuesInFramesTest$MainDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+      classWriter.visitSource("LongStackValuesInFramesTest.java", null);
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Main",
+          "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Tester",
+          "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest",
+          "Tester",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitLdcInsn(10L);
+        methodVisitor.visitIntInsn(BIPUSH, 42);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitInsn(ARRAYLENGTH);
+        Label label1 = new Label();
+        methodVisitor.visitJumpInsn(IFNE, label1);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "com/android/tools/r8/cf/stackmap/LongStackValuesInFramesTest$Tester",
+            "test",
+            "(JI)V",
+            false);
+        Label label2 = new Label();
+        methodVisitor.visitJumpInsn(Opcodes.GOTO, label2);
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            1,
+            new Object[] {"[Ljava/lang/String;"},
+            2,
+            new Object[] {Opcodes.LONG, Opcodes.INTEGER});
+        methodVisitor.visitInsn(Opcodes.POP);
+        methodVisitor.visitInsn(Opcodes.POP2);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 0, null);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(4, 3);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
new file mode 100644
index 0000000..113a40f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.cf.stackmap.UninitializedGetFieldTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedGetFieldTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"Main::foo"};
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public UninitializedGetFieldTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(dump())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(VerifyError.class);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8Cf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningThatMatches(
+                  diagnosticMessage(containsString("The expected type uninitialized")));
+            });
+  }
+
+  @Test
+  public void testD8Dex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningThatMatches(
+                  diagnosticMessage(containsString("The expected type uninitialized")));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(VerifyError.class);
+  }
+
+  public static class Main {
+
+    private Object object;
+
+    private Main() {
+      this.object = new Object();
+      foo(this.object);
+    }
+
+    private void foo(Object object) {
+      System.out.println("Main::foo");
+    }
+
+    public static void main(String[] args) {
+      new Main();
+    }
+  }
+
+  // The dump is generated from the above code. The change made is to Main::<init> where we
+  // now putfield and getfield before initializing.
+  static class MainDump implements Opcodes {
+
+    static byte[] dump() throws Exception {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+          "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        fieldVisitor =
+            classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+            "object",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+            "object",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+            "foo",
+            "(Ljava/lang/Object;)V",
+            false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(3, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Main::foo");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 2);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitTypeInsn(
+            NEW, "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/cf/stackmap/UninitializedGetFieldTest$Main",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitInsn(POP);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
new file mode 100644
index 0000000..30b2a78
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedInstanceOfTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedInstanceOfTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(dump())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(VerifyError.class);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8Cf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The expected type uninitialized new is not assignable"));
+              diagnostics.assertErrorMessageThatMatches(
+                  containsString("Could not validate stack map frames"));
+            });
+  }
+
+  @Test()
+  public void testD8Dex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    boolean expectFailure = parameters.getDexRuntimeVersion().isAtLeast(Version.V7_0_0);
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The expected type uninitialized new is not assignable"));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            expectFailure,
+            result -> result.assertFailureWithErrorThatThrows(VerifyError.class),
+            TestRunResult::assertSuccessWithOutputLines);
+  }
+
+  public UninitializedInstanceOfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static class Main {
+
+    // The dump is generated from the following code, where we just swap the instanceof with the
+    // initializer call.
+    public static void main(String[] args) {
+      boolean m = (new Object() instanceof Main);
+    }
+  }
+
+  static class MainDump implements Opcodes {
+
+    static byte[] dump() throws Exception {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main",
+          "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+        methodVisitor.visitInsn(DUP);
+        // INSTANCEOF and INVOKESPECIAL is swapped.
+        methodVisitor.visitTypeInsn(
+            INSTANCEOF, "com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest$Main");
+        methodVisitor.visitVarInsn(ISTORE, 1);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 2);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
new file mode 100644
index 0000000..86429af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedNewCheckCastTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedNewCheckCastTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(dump())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(VerifyError.class);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8Cf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The expected type uninitialized new is not assignable"));
+              diagnostics.assertErrorMessageThatMatches(
+                  containsString("Could not validate stack map frames"));
+            });
+  }
+
+  @Test
+  public void testD8Dex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The expected type uninitialized new is not assignable"));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(VerifyError.class);
+  }
+
+  public UninitializedNewCheckCastTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static class Main {
+
+    // The dump is generated from the following code, where we just swap the CheckCast with the
+    // initializer call.
+    public static void main(String[] args) {
+      Main m = (Main) new Object();
+    }
+  }
+
+  static class MainDump implements Opcodes {
+
+    static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main",
+          "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+        methodVisitor.visitInsn(DUP);
+        // CHECK-CAST has swapped position with INVOKESPECIAL.
+        methodVisitor.visitTypeInsn(
+            CHECKCAST, "com/android/tools/r8/cf/stackmap/UninitializedNewCheckCastTest$Main");
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitVarInsn(ASTORE, 1);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 2);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
new file mode 100644
index 0000000..9a52445
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldSelfTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedPutFieldSelfTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(dump())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(VerifyError.class);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8Cf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The expected type uninitialized this is not assignable"));
+              diagnostics.assertErrorMessageThatMatches(
+                  containsString("Could not validate stack map frames"));
+            });
+  }
+
+  @Test
+  public void testD8Dex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    boolean willFailVerification =
+        parameters.getDexRuntimeVersion().isOlderThan(Version.V5_1_1)
+            || parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1);
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertWarningMessageThatMatches(
+                  containsString("The expected type uninitialized this is not assignable"));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrowsIf(willFailVerification, VerifyError.class)
+        .assertSuccessWithOutputLinesIf(!willFailVerification, "Main::foo");
+  }
+
+  public UninitializedPutFieldSelfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static class Main {
+
+    private Main main;
+
+    private Main() {
+      this.main = this;
+      this.main.foo();
+    }
+
+    private void foo() {
+      System.out.println("Main::foo");
+    }
+
+    public static void main(String[] args) {
+      new Main();
+    }
+  }
+
+  // The dump is generated from the above code. The change that is made is to Main::<init> where we
+  // now putfield before initializing. That will try and assign an uninstantiated type to a field
+  // which is not allowed.
+  public static class MainDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+          "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_PRIVATE,
+                "main",
+                "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+            "main",
+            "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+            "main",
+            "Lcom/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main;");
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+            "foo",
+            "()V",
+            false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Main::foo");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitTypeInsn(
+            NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldSelfTest$Main",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitInsn(POP);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java
new file mode 100644
index 0000000..e943ebc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.cf.stackmap.UninitializedPutFieldTest.MainDump.dump;
+import static org.hamcrest.CoreMatchers.containsString;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class UninitializedPutFieldTest extends TestBase {
+
+  private final String[] EXPECTED = new String[] {"Main::foo"};
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(dump())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test()
+  public void testD8Cf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertNoWarningsMatch(
+                  diagnosticMessage(containsString("The expected type uninitialized")));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8Dex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(dump())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertNoWarningsMatch(
+                  diagnosticMessage(containsString("The expected type uninitialized")));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public UninitializedPutFieldTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public static class Main {
+
+    private Object object;
+
+    private Main() {
+      this.object = new Object();
+      foo(this.object);
+    }
+
+    private void foo(Object object) {
+      System.out.println("Main::foo");
+    }
+
+    public static void main(String[] args) {
+      new Main();
+    }
+  }
+
+  // The dump is generated from the above code. The change made is to Main::<init> where we
+  // now putfield before initializing this.
+  static class MainDump implements Opcodes {
+
+    static byte[] dump() throws Exception {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+          "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        fieldVisitor =
+            classWriter.visitField(ACC_PRIVATE, "object", "Ljava/lang/Object;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+            "object",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+            "object",
+            "Ljava/lang/Object;");
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+            "foo",
+            "(Ljava/lang/Object;)V",
+            false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(3, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(ACC_PRIVATE, "foo", "(Ljava/lang/Object;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Main::foo");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 2);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitTypeInsn(
+            NEW, "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/cf/stackmap/UninitializedPutFieldTest$Main",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitInsn(POP);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 4161d87..fcc79dc 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.cf.CfCodePrinter;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.origin.Origin;
 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.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
@@ -66,6 +68,10 @@
     return Paths.get(ToolHelper.SOURCE_DIR, getGeneratedType().getInternalName() + ".java");
   }
 
+  protected CfCode getCode(String holderName, String methodName, CfCode code) {
+    return code;
+  }
+
   private String getGeneratedClassName() {
     return getGeneratedType().getName();
   }
@@ -94,7 +100,7 @@
   }
 
   private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
-    InternalOptions options = new InternalOptions();
+    InternalOptions options = new InternalOptions(factory, new Reporter());
     options.testing.readInputStackMaps = true;
     JarClassFileReader<DexProgramClass> reader =
         new JarClassFileReader<>(
@@ -104,9 +110,13 @@
                 if (method.isInitializer()) {
                   continue;
                 }
-                String methodName =
-                    method.getHolderType().getName() + "_" + method.method.name.toString();
-                codePrinter.visitMethod(methodName, method.getCode().asCfCode());
+                String holderName = method.getHolderType().getName();
+                String methodName = method.method.name.toString();
+                String generatedMethodName = holderName + "_" + methodName;
+                CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
+                if (code != null) {
+                  codePrinter.visitMethod(generatedMethodName, code);
+                }
               }
             },
             ClassKind.PROGRAM);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
index d84c164..5b2dbe7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -35,10 +35,8 @@
     return codeInspector.allClasses().stream()
         .filter(
             clazz ->
-                clazz.isSynthetic()
-                    && clazz
-                        .getOriginalName()
-                        .endsWith(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX))
+                SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument(
+                    clazz.getOriginalReference()))
         .findFirst()
         .get();
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
index 63c1f18..f7aacb5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -15,7 +14,6 @@
 import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import java.util.Set;
 import java.util.stream.Collectors;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class JavaLambdaMergingTest extends HorizontalClassMergingTestBase {
@@ -25,17 +23,13 @@
   }
 
   @Test
-  @Ignore("b/174809311): Test does not work with hygienic lambdas. Rewrite or remove")
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addOptionsModification(
-            options -> {
-              options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
-              assertFalse(options.horizontalClassMergerOptions().isJavaLambdaMergingEnabled());
-              options.horizontalClassMergerOptions().enableJavaLambdaMerging();
-            })
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addHorizontallyMergedClassesInspectorIf(
             enableHorizontalClassMerging && parameters.isDexRuntime(),
             inspector -> {
@@ -59,7 +53,7 @@
   }
 
   private static boolean isLambda(DexType type) {
-    return SyntheticItemsTestUtils.isExternalLambda(
+    return SyntheticItemsTestUtils.isInternalLambda(
         Reference.classFromDescriptor(type.toDescriptorString()));
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
new file mode 100644
index 0000000..e7eda20
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class MergedVirtualMethodStaticizerTest extends HorizontalClassMergingTestBase {
+  public MergedVirtualMethodStaticizerTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(Program.class)
+        .addKeepClassAndMembersRules(Program.Main.class)
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class))
+        .run(parameters.getRuntime(), Program.Main.class)
+        .assertSuccessWithOutputLines("A::foo", "Staticized::foo", "B::foo");
+  }
+
+  public static class Program {
+
+    @NeverClassInline
+    public static class Staticized {
+      public static final Staticized staticized = new Staticized();
+
+      @NeverInline
+      public void foo() {
+        System.out.println("Staticized::foo");
+      }
+    }
+
+    @NeverClassInline
+    public static class A {
+
+      public void foo() {
+        System.out.println("A::foo");
+        Staticized.staticized.foo();
+      }
+    }
+
+    @NeverClassInline
+    public static class B {
+
+      public void foo() {
+        System.out.println("B::foo");
+      }
+    }
+
+    public static class Main {
+
+      public static void main(String[] args) {
+        new A().foo();
+        new B().foo();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index 74d0ad6..441934e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -33,6 +34,9 @@
         .addOptionsModification(
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector -> inspector.assertIsCompleteMergeGroup(A.class, C.class))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -45,7 +49,8 @@
               assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
               assertThat(codeInspector.clazz(B.class), isPresent());
-              assertThat(codeInspector.clazz(C.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
             });
   }
 
@@ -57,6 +62,7 @@
   @Target({ElementType.METHOD})
   public @interface MethodAnnotation {}
 
+  @TypeAnnotation
   @NeverClassInline
   public static class A {
     public A() {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
index 6f3c8f8..dbc83c8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
@@ -99,6 +99,7 @@
     }
   }
 
+  @NeverClassInline
   public static class C extends Parent implements I {
     @Override
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
index 627838f..169a59b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
@@ -99,6 +99,7 @@
     }
   }
 
+  @NeverClassInline
   public static class C extends Parent implements I {
     @Override
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index 635ec43..11164b1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
@@ -15,6 +16,7 @@
 import org.junit.Test;
 
 public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase {
+
   public OverrideAbstractMethodWithDefaultTest(
       TestParameters parameters, boolean enableHorizontalClassMerging) {
     super(parameters, enableHorizontalClassMerging);
@@ -29,6 +31,7 @@
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspectorIf(
@@ -73,8 +76,10 @@
     }
   }
 
+  @NeverClassInline
   static class C1 extends B1 implements J {}
 
+  @NeverClassInline
   static class C2 extends B2 {}
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index 0e3a098..15ca564 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
@@ -29,6 +30,7 @@
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspectorIf(
@@ -52,8 +54,10 @@
     }
   }
 
+  @NeverClassInline
   public static class A implements I {}
 
+  @NeverClassInline
   public static class B implements I {
     @NeverInline
     @Override
@@ -69,6 +73,7 @@
     }
   }
 
+  @NeverClassInline
   public static class C extends A implements J {}
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index fa2dcb2..2aea3d0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -30,6 +31,7 @@
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -58,8 +60,10 @@
 
   public static class Parent implements I {}
 
+  @NeverClassInline
   public static class A extends Parent {}
 
+  @NeverClassInline
   public static class B extends Parent {
     @NeverInline
     @Override
@@ -68,6 +72,7 @@
     }
   }
 
+  @NeverClassInline
   @NoUnusedInterfaceRemoval
   @NoVerticalClassMerging
   interface J extends I {
@@ -76,6 +81,7 @@
     }
   }
 
+  @NeverClassInline
   public static class C extends A implements J {}
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 646dbf9..784e875 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -102,7 +102,10 @@
           .apply(this::configureProgram)
           .setIncludeClassesChecksum(true)
           .compile()
-          .run(parameters.getRuntime(), testClassName)
+          .run(
+              parameters.getRuntime(),
+              testClassName,
+              parameters.getRuntime().asDex().getVm().getVersion().toString())
           .assertSuccess()
           .inspect(this::assertDesugaring);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java
new file mode 100644
index 0000000..a2e1120
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportJava9Test.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class IntegerBackportJava9Test extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  private static final Path TEST_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+
+  public IntegerBackportJava9Test(TestParameters parameters) {
+    super(parameters, Short.class, TEST_JAR, "backport.IntegerBackportJava9Main");
+    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+    // an actual API level, migrate these tests to IntegerBackportTest.
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
new file mode 100644
index 0000000..4bd90c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportJava9Test.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class LongBackportJava9Test extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  private static final Path TEST_JAR =
+      Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION);
+
+  public LongBackportJava9Test(TestParameters parameters) {
+    super(parameters, Short.class, TEST_JAR, "backport.LongBackportJava9Main");
+    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+    // an actual API level, migrate these tests to LongBackportTest.
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
index 90c8009..bf4f43f 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
@@ -67,8 +67,8 @@
           "false",
           "Empty lambda",
           "true", // R8 will eliminate the call to the impl method thus making lambdas equal.
-          "true",
-          "true");
+          "false",
+          "false");
 
   private final TestParameters parameters;
 
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index c68ec40..2e97d1e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -57,7 +57,7 @@
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index 811d24a..5fce5e2 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -57,7 +57,7 @@
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index fc7a247..071af85 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -70,7 +70,7 @@
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index eca975d..15684fe 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -67,7 +67,7 @@
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
index 3a0a306..7589764 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -99,7 +99,8 @@
   }
 
   public static void assertRecordsAreRecords(Path output) throws IOException {
-    CodeInspector inspector = new CodeInspector(output, opt -> opt.testing.canUseRecords = true);
+    CodeInspector inspector =
+        new CodeInspector(output, opt -> opt.testing.enableExperimentalRecordDesugaring = true);
     for (FoundClassSubject clazz : inspector.allClasses()) {
       if (clazz.getDexProgramClass().superType.toString().equals("java.lang.Record")) {
         assertTrue(clazz.getDexProgramClass().isRecord());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index e6d1444..610b061 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -61,7 +61,7 @@
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index ff540d1..d5d2b1a 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -36,20 +37,37 @@
   public static List<Object[]> data() {
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevels()
+            .build());
   }
 
   @Test
-  public void testJvm() throws Exception {
-    testForJvm()
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      // TODO(b/179146128): Add a test for D8 cf to cf.
+      return;
+    }
+    testForD8()
         .addProgramClassFileData(PROGRAM_DATA)
-        .enablePreview()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8Cf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     Path output =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
@@ -58,7 +76,7 @@
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     RecordTestUtils.assertRecordsAreRecords(output);
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 81ae312..1584f41 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -122,11 +122,11 @@
             DexEncodedMethod.EMPTY_ARRAY,
             new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)},
             false,
-            DexProgramClass::invalidChecksumRequest,
-            synthesizedFrom);
+            DexProgramClass::invalidChecksumRequest);
     return programClass;
   }
 
+  // TODO(b/181636450): Reconsider this test as it no longer reflects the compiler synthetics.
   @Test
   public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException {
     InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
@@ -154,12 +154,15 @@
     builder.addSynthesizedClass(sharedSynthesizedClass);
     classes.forEach(builder::addProgramClass);
     DexApplication application = builder.build();
+    AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(application));
+    classes.forEach(
+        c -> appView.getSyntheticItems().addLegacySyntheticClass(sharedSynthesizedClass, c));
 
     CollectInfoConsumer consumer = new CollectInfoConsumer();
     options.programConsumer = consumer;
     ApplicationWriter writer =
         new ApplicationWriter(
-            AppView.createForD8(AppInfo.createInitialAppInfo(application)),
+            appView,
             null,
             GraphLens.getIdentityLens(),
             InitClassLens.getDefault(),
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 278d99c..96220f8 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -75,7 +75,7 @@
   @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
-    Consumer<R8FullTestBuilder> configurator =
+    ThrowableConsumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
             r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
     ProcessResult processResult =
@@ -84,7 +84,7 @@
             ImmutableSet.of(BaseSuperClass.class),
             ImmutableSet.of(FeatureClass.class),
             FeatureClass.class,
-            ConsumerUtils.emptyThrowingConsumer(),
+            ThrowableConsumer.empty(),
             configurator);
 
     assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index e6d0ee9..938a8f6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -77,7 +77,7 @@
             ImmutableSet.of(BaseSuperClass.class),
             ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
             FeatureClass.class,
-            ConsumerUtils.emptyThrowingConsumer(),
+            ThrowableConsumer.empty(),
             R8TestBuilder::enableInliningAnnotations);
     assertEquals(processResult.exitCode, 0);
     assertEquals(processResult.stdout, EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 780696e..aa298a7 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -80,7 +80,7 @@
   @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
-    Consumer<R8FullTestBuilder> configurator =
+    ThrowableConsumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
             r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
     ProcessResult processResult =
@@ -89,7 +89,7 @@
             ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
             ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
             FeatureClass.class,
-            ConsumerUtils.emptyThrowingConsumer(),
+            ThrowableConsumer.empty(),
             configurator);
 
     assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
index 46ac75d..496c3d3 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -15,12 +15,11 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
-import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,10 +48,10 @@
   @Test
   public void testInlining() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    Consumer<R8FullTestBuilder> configurator =
+    ThrowableConsumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
             r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureInlined =
+    ThrowableConsumer<R8TestCompileResult> ensureInlined =
         r8TestCompileResult -> {
           // Ensure that isEarly from BaseUtilClass is inlined into the feature
           ClassSubject clazz = r8TestCompileResult.inspector().clazz(BaseUtilClass.class);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index dbb1e76..05d6d3d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -119,14 +120,14 @@
     return builder.build();
   }
 
-  <E extends Throwable> ProcessResult testR8Splitter(
+  public ProcessResult testR8Splitter(
       TestParameters parameters,
       Set<Class<?>> baseClasses,
       Set<Class<?>> featureClasses,
       Class<?> toRun,
-      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
-      Consumer<R8FullTestBuilder> r8TestConfigurator)
-      throws IOException, CompilationFailedException, E {
+      ThrowableConsumer<R8TestCompileResult> compileResultConsumer,
+      ThrowableConsumer<R8FullTestBuilder> r8TestConfigurator)
+      throws IOException, CompilationFailedException {
     R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend());
     if (parameters.isCfRuntime()) {
       // Compiling to jar we need to support the same way of loading code at runtime as
@@ -136,19 +137,19 @@
           .addKeepClassAndMembersRules(PathClassLoader.class);
     }
 
-    r8FullTestBuilder
-        .addProgramClasses(SplitRunner.class, RunInterface.class)
-        .addProgramClasses(baseClasses)
-        .addFeatureSplit(featureClasses.toArray(new Class[0]))
-        .addInliningAnnotations()
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(SplitRunner.class)
-        .addKeepClassRules(toRun);
+    R8TestCompileResult r8TestCompileResult =
+        r8FullTestBuilder
+            .addProgramClasses(SplitRunner.class, RunInterface.class)
+            .addProgramClasses(baseClasses)
+            .addFeatureSplit(featureClasses.toArray(new Class[0]))
+            .addInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(SplitRunner.class)
+            .addKeepClassRules(toRun)
+            .apply(r8TestConfigurator)
+            .compile()
+            .apply(compileResultConsumer);
 
-    r8TestConfigurator.accept(r8FullTestBuilder);
-
-    R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
-    compileResultConsumer.accept(r8TestCompileResult);
     Path baseOutput = r8TestCompileResult.writeToZip();
     return runFeatureOnArt(
         toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
index 20c2b51..61b0fd7 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -14,15 +14,12 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableSet;
-import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,31 +48,25 @@
   @Test
   public void testDistribution() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder ->
-            r8FullTestBuilder
-                .noMinification()
-                .enableInliningAnnotations()
-                .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureLambdaNotInBase =
-        r8TestCompileResult -> {
-          r8TestCompileResult.inspect(
-              base ->
-                  assertFalse(base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)),
-              feature ->
-                  assertTrue(
-                      feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)));
-        };
+    ThrowableConsumer<R8TestCompileResult> ensureLambdaNotInBase =
+        r8TestCompileResult ->
+            r8TestCompileResult.inspect(
+                base ->
+                    assertFalse(
+                        base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)),
+                feature ->
+                    assertTrue(
+                        feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)));
     ProcessResult processResult =
         testR8Splitter(
             parameters,
-            ImmutableSet.of(BaseSuperClass.class),
-            ImmutableSet.of(FeatureClass.class, MyFunction.class),
+            ImmutableSet.of(BaseSuperClass.class, MyFunction.class),
+            ImmutableSet.of(FeatureClass.class),
             FeatureClass.class,
             ensureLambdaNotInBase,
-            configurator);
-    assertEquals(processResult.exitCode, 0);
-    assertEquals(processResult.stdout, StringUtils.lines("42foobar"));
+            this::configure);
+    assertEquals(0, processResult.exitCode);
+    assertEquals(StringUtils.lines("42foobar"), processResult.stdout);
   }
 
   @Test
@@ -87,13 +78,7 @@
             .addFeatureSplit(FeatureClass.class)
             .addFeatureSplit(Feature2Class.class)
             .addKeepFeatureMainRules(BaseSuperClass.class, FeatureClass.class, Feature2Class.class)
-            .addKeepMethodRules(
-                Reference.methodFromMethod(
-                    BaseSuperClass.class.getDeclaredMethod(
-                        "keptApplyLambda", MyFunction.class, String.class)))
-            .noMinification()
-            .enableInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
+            .apply(this::configure)
             .compile();
 
     compileResult
@@ -105,6 +90,17 @@
         .assertSuccessWithOutputLines("43barfoo");
   }
 
+  private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException {
+    testBuilder
+        .addKeepMethodRules(
+            Reference.methodFromMethod(
+                BaseSuperClass.class.getDeclaredMethod(
+                    "keptApplyLambda", MyFunction.class, String.class)))
+        .enableInliningAnnotations()
+        .noMinification()
+        .setMinApi(parameters.getApiLevel());
+  }
+
   public abstract static class BaseSuperClass implements RunInterface {
     @Override
     public void run() {
diff --git a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
index ede7822..7c8eafe 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
@@ -5,6 +5,7 @@
 
 import static java.util.stream.Collectors.toList;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -20,7 +21,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.stream.Stream;
 import org.junit.Assume;
@@ -32,6 +32,16 @@
 public class DumpMainDexInputsTest extends TestBase {
 
   private final TestParameters parameters;
+  private final String mainDexRulesForMainDexFile1AndMainDexFile2 =
+      StringUtils.lines(
+          "-keep class " + MainDexFile1.class.getTypeName() + " {",
+          "  *;",
+          "}",
+          "-keep class " + MainDexFile2.class.getTypeName() + " {",
+          "  *;",
+          "}");
+  private final String mainDexRulesForMainDexFile3 =
+      "-keep class " + MainDexFile3.class.getTypeName();
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -42,14 +52,33 @@
     this.parameters = parameters;
   }
 
-  private Path newMainDexListPath() throws IOException {
-    Path mainDexPath = temp.newFile("main-dex-list.txt").toPath();
+  private Path mainDexListForMainDexFile1AndMainDexFile2() throws IOException {
+    Path mainDexPath = temp.newFile("mylist1.txt").toPath();
     String mainDexList =
         StringUtils.lines(toMainDexFormat(MainDexFile1.class), toMainDexFormat(MainDexFile2.class));
     Files.write(mainDexPath, mainDexList.getBytes());
     return mainDexPath;
   }
 
+  private Path mainDexListForMainDexFile3() throws IOException {
+    Path mainDexPath = temp.newFile("mylist2.txt").toPath();
+    String mainDexList = StringUtils.lines(toMainDexFormat(MainDexFile3.class));
+    Files.write(mainDexPath, mainDexList.getBytes());
+    return mainDexPath;
+  }
+
+  private Path newMainDexRulesPath1() throws IOException {
+    Path mainDexPath = temp.newFile("myrules1.txt").toPath();
+    Files.write(mainDexPath, mainDexRulesForMainDexFile1AndMainDexFile2.getBytes());
+    return mainDexPath;
+  }
+
+  private Path newMainDexRulesPath2() throws IOException {
+    Path mainDexPath = temp.newFile("myrules2.txt").toPath();
+    Files.write(mainDexPath, mainDexRulesForMainDexFile3.getBytes());
+    return mainDexPath;
+  }
+
   private static String toMainDexFormat(Class<?> clazz) {
     return clazz.getName().replace(".", "/") + FileUtils.CLASS_EXTENSION;
   }
@@ -63,7 +92,8 @@
     testForD8()
         .setMinApi(parameters.getApiLevel())
         .addInnerClasses(DumpMainDexInputsTest.class)
-        .addMainDexListFiles(Collections.singleton(newMainDexListPath()))
+        .addMainDexListFiles(
+            mainDexListForMainDexFile1AndMainDexFile2(), mainDexListForMainDexFile3())
         .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
         .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
         .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
@@ -74,24 +104,64 @@
                 "Dumping main dex list resources may have side effects due to I/O on Paths."))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world");
-    verifyDumpDir(dumpDir);
+    verifyDumpDir(dumpDir, true);
   }
 
-  private void verifyDumpDir(Path dumpDir) throws IOException {
+  @Test
+  public void testD8MainDexRules() throws Exception {
+    Assume.assumeTrue(
+        "pre-native-multidex only",
+        parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K));
+    Path dumpDir = temp.newFolder().toPath();
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(DumpMainDexInputsTest.class)
+        .addMainDexRulesFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
+        .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
+        .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+        .compile()
+        .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+    verifyDumpDir(dumpDir, false);
+  }
+
+  @Test
+  public void testR8MainDexRules() throws Exception {
+    Assume.assumeTrue(
+        "pre-native-multidex only",
+        parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K));
+    Path dumpDir = temp.newFolder().toPath();
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(DumpMainDexInputsTest.class)
+        .addMainDexRuleFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
+        .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
+        .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+        .addKeepAllClassesRule()
+        .allowDiagnosticMessages()
+        .compile()
+        .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello, world");
+    verifyDumpDir(dumpDir, false);
+  }
+
+  private void verifyDumpDir(Path dumpDir, boolean checkMainDexList) throws IOException {
     assertTrue(Files.isDirectory(dumpDir));
     List<Path> paths = Files.walk(dumpDir, 1).collect(toList());
     boolean hasVerified = false;
     for (Path path : paths) {
       if (!path.equals(dumpDir)) {
         // The non-external run here results in assert code calling application read.
-        verifyDump(path);
+        verifyDump(path, checkMainDexList);
         hasVerified = true;
       }
     }
     assertTrue(hasVerified);
   }
 
-  private void verifyDump(Path dumpFile) throws IOException {
+  private void verifyDump(Path dumpFile, boolean checkMainDexList) throws IOException {
     assertTrue(Files.exists(dumpFile));
     Path unzipped = temp.newFolder().toPath();
     ZipUtils.unzip(dumpFile.toString(), unzipped.toFile());
@@ -99,20 +169,27 @@
     assertTrue(Files.exists(unzipped.resolve("program.jar")));
     assertTrue(Files.exists(unzipped.resolve("library.jar")));
     assertTrue(Files.exists(unzipped.resolve("main-dex-list.txt")));
-    String mainDex = new String(Files.readAllBytes(unzipped.resolve("main-dex-list.txt")));
-    List<String> mainDexLines =
-        Arrays.stream(mainDex.split("\n")).filter(s -> !s.isEmpty()).collect(toList());
-    assertEquals(5, mainDexLines.size());
-    List<String> expected =
-        Stream.of(
-                MainDexFile1.class,
-                MainDexFile2.class,
-                MainDexClass1.class,
-                MainDexClass2.class,
-                TestClass.class)
-            .map(DumpMainDexInputsTest::toMainDexFormat)
-            .collect(toList());
-    assertEquals(expected, mainDexLines);
+    if (checkMainDexList) {
+      String mainDex = new String(Files.readAllBytes(unzipped.resolve("main-dex-list.txt")));
+      List<String> mainDexLines =
+          Arrays.stream(mainDex.split("\n")).filter(s -> !s.isEmpty()).collect(toList());
+      assertEquals(6, mainDexLines.size());
+      List<String> expected =
+          Stream.of(
+                  MainDexFile1.class,
+                  MainDexFile2.class,
+                  MainDexFile3.class,
+                  MainDexClass1.class,
+                  MainDexClass2.class,
+                  TestClass.class)
+              .map(DumpMainDexInputsTest::toMainDexFormat)
+              .collect(toList());
+      assertEquals(expected, mainDexLines);
+    } else {
+      String mainDexRules = new String(Files.readAllBytes(unzipped.resolve("main-dex-rules.txt")));
+      assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile1AndMainDexFile2));
+      assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile3));
+    }
   }
 
   static class TestClass {
@@ -129,6 +206,8 @@
 
   static class MainDexFile2 {}
 
+  static class MainDexFile3 {}
+
   static class NonMainDex1 {}
 
   static class NonMainDex2 {}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
index 3a09469..a9e28c9 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.StringConsumer.FileConsumer;
-import com.android.tools.r8.utils.AndroidApp;
 import java.io.File;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -15,12 +14,11 @@
   @Test
   public void buildAndTreeShakeFromDeployJar() throws Exception {
     Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
-    AndroidApp app =
-        buildAndTreeShakeFromDeployJar(
-            CompilationMode.RELEASE,
-            GMSCORE_V9_DIR,
-            true,
-            GMSCORE_V9_MAX_SIZE,
-            options -> options.proguardMapConsumer = new FileConsumer(proguardMapPath));
+    buildAndTreeShakeFromDeployJar(
+        CompilationMode.RELEASE,
+        GMSCORE_V9_DIR,
+        true,
+        GMSCORE_V9_MAX_SIZE,
+        options -> options.proguardMapConsumer = new FileConsumer(proguardMapPath));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
index 9461c6b..1950e53 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCanBePostponedTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.sideeffect;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -48,7 +49,7 @@
 
   private void inspect(CodeInspector inspector) {
     ClassSubject classSubject = inspector.clazz(A.class);
-    assertThat(classSubject, isPresent());
+    assertThat(classSubject, isAbsent());
 
     // A.inlineable() should be inlined because we should be able to determine that A.<clinit>() can
     // safely be postponed.
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index d78a1c7..455c5c2 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -9,7 +9,11 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
@@ -17,6 +21,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -73,6 +78,44 @@
     return 2020;
   }
 
+  private static CfInstruction rewriteToJava9API(
+      DexItemFactory itemFactory, CfInstruction instruction) {
+    // Rewrite static invoke of javaUtilLongParseUnsignedLongStub to j.l.Long.parseUnsignedLong.
+    if (instruction.isInvoke()
+        && instruction
+            .asInvoke()
+            .getMethod()
+            .getName()
+            .toString()
+            .equals("javaLangLongParseUnsignedLongStub")) {
+      CfInvoke invoke = instruction.asInvoke();
+      return new CfInvoke(
+          invoke.getOpcode(),
+          itemFactory.createMethod(
+              itemFactory.createType("Ljava/lang/Long;"),
+              invoke.getMethod().getProto(),
+              itemFactory.createString("parseUnsignedLong")),
+          invoke.isInterface());
+    } else {
+      return instruction;
+    }
+  }
+
+  @Override
+  protected CfCode getCode(String holderName, String methodName, CfCode code) {
+    if (methodName.endsWith("Stub")) {
+      // Don't include stubs targeted only for rewriting in the generated code.
+      return null;
+    }
+    if (holderName.equals("LongMethods") && methodName.equals("parseUnsignedLongWithRadix")) {
+      code.setInstructions(
+          code.getInstructions().stream()
+              .map(instruction -> rewriteToJava9API(factory, instruction))
+              .collect(Collectors.toList()));
+    }
+    return code;
+  }
+
   @Test
   public void testBackportsGenerated() throws Exception {
     ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
index 0560b97..a3bd157 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
@@ -57,4 +57,9 @@
     long asLong = i & 0xffffffffL;
     return Long.toString(asLong, radix);
   }
+
+  public static int parseIntSubsequenceWithRadix(
+      CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException {
+    return Integer.parseInt(s.subSequence(beginIndex, endIndex).toString(), radix);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
index 643d295..65a60ef 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
@@ -10,6 +10,11 @@
     return (int) (l ^ (l >>> 32));
   }
 
+  public static long parseLongSubsequenceWithRadix(
+      CharSequence s, int beginIndex, int endIndex, int radix) {
+    return Long.parseLong(s.subSequence(beginIndex, endIndex).toString(), radix);
+  }
+
   public static long divideUnsigned(long dividend, long divisor) {
     // This implementation is adapted from Guava's UnsignedLongs.java and Longs.java.
 
@@ -92,10 +97,20 @@
     return Long.parseUnsignedLong(s, 10);
   }
 
+  private static long javaLangLongParseUnsignedLongStub(
+      CharSequence s, int beginIndex, int endIndex, int radix) {
+    throw new RuntimeException("Stub invoked");
+  }
+
   public static long parseUnsignedLongWithRadix(String s, int radix) {
+    return javaLangLongParseUnsignedLongStub(s, 0, s.length(), radix);
+  }
+
+  public static long parseUnsignedLongSubsequenceWithRadix(
+      CharSequence s, int beginIndex, int endIndex, int radix) {
     // This implementation is adapted from Guava's UnsignedLongs.java
 
-    int length = s.length();
+    int length = endIndex - beginIndex;
     if (length == 0) {
       throw new NumberFormatException("empty string");
     }
@@ -106,23 +121,23 @@
     long maxValueBeforeRadixMultiply = Long.divideUnsigned(-1L, radix);
 
     // If the string starts with '+' and contains at least two characters, skip the plus.
-    int start = s.charAt(0) == '+' && length > 1 ? 1 : 0;
+    int start = s.charAt(beginIndex) == '+' && length > 1 ? beginIndex + 1 : beginIndex;
 
     long value = 0;
-    for (int pos = start; pos < length; pos++) {
+    for (int pos = start; pos < endIndex; pos++) {
       int digit = Character.digit(s.charAt(pos), radix);
       if (digit == -1) {
-        throw new NumberFormatException(s);
+        throw new NumberFormatException(s.toString());
       }
-      if (// high bit is already set
-          value < 0
+      if ( // high bit is already set
+      value < 0
           // or radix multiply will overflow
           || value > maxValueBeforeRadixMultiply
           // or digit add will overflow after radix multiply
           || (value == maxValueBeforeRadixMultiply
               && digit > (int) Long.remainderUnsigned(-1L, radix))) {
         // Explicit String.concat to work around https://issuetracker.google.com/issues/136596951.
-        throw new NumberFormatException("Too large for unsigned long: ".concat(s));
+        throw new NumberFormatException("Too large for unsigned long: ".concat(s.toString()));
       }
       value = (value * radix) + digit;
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
index 1740c8a..6001549 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -73,7 +73,7 @@
         .addKeepMainRule(Main.class)
         .addOptionsModification(
             options -> {
-              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.ALWAYS);
               options.testing.inlineeIrModifier = this::modifyIr;
             })
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
index 1d1f00e..b1e3cfc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
@@ -77,24 +77,12 @@
 
   private void inspect(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(TestClass.class);
-    if (parameters.isCfRuntime()) {
-      assertThat(inspector.clazz(PairBuilder.class), isPresent());
+    assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
 
-      // const-string canonicalization is disabled in CF, which helps ClassInliner identify
-      // PairBuilder as candidate.
-      Set<String> expected =
-          ImmutableSet.of(StringBuilder.class.getTypeName(), PairBuilder.class.getTypeName());
-      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
-      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
-      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
-    } else {
-      assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
-
-      Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName());
-      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
-      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
-      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
-    }
+    Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName());
+    assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
+    assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
+    assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
index 4f020fd..24c1c8b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.references.Reference.methodFromMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
@@ -113,8 +114,9 @@
                             .holder
                             .toString()
                             .equals(SimpleLibraryOverride.class.getTypeName())));
-    // Check the non-simple run is not inlined.
-    assertTrue(
+    // TODO(b/181942160): The non-simple run should ideally not inlined by the class inliner, since
+    //  there is an instance of NonSimpleLibraryOverride that is not eligible for class inlining.
+    assertFalse(
         "Expected NonSimple.run invoke in:\n" + main.getMethod().codeToString(),
         main.streamInstructions()
             .anyMatch(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java
new file mode 100644
index 0000000..9ddf887
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnConstantClassIdAfterBranchPruningTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+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.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 SwitchOnConstantClassIdAfterBranchPruningTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SwitchOnConstantClassIdAfterBranchPruningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class))
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertEquals(0, aClassSubject.allInstanceFields().size());
+
+              MethodSubject mMethodSubject =
+                  aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+              assertThat(mMethodSubject, isPresent());
+              assertTrue(
+                  mMethodSubject.streamInstructions().noneMatch(x -> x.isIf() || x.isSwitch()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().m();
+      if (alwaysFalse()) {
+        new B().m();
+        new C().m();
+      }
+    }
+
+    static boolean alwaysFalse() {
+      return false;
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    void m() {
+      System.out.println("A");
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    void m() {
+      System.out.println("B");
+    }
+  }
+
+  @NeverClassInline
+  static class C {
+
+    void m() {
+      System.out.println("C");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java
new file mode 100644
index 0000000..a2bfb2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/SwitchOnNonConstantClassIdAfterBranchPruningTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+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.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 SwitchOnNonConstantClassIdAfterBranchPruningTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SwitchOnNonConstantClassIdAfterBranchPruningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class))
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertEquals(1, aClassSubject.allInstanceFields().size());
+
+              MethodSubject mMethodSubject =
+                  aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+              assertThat(mMethodSubject, isPresent());
+              assertTrue(
+                  mMethodSubject
+                      .streamInstructions()
+                      .noneMatch(instruction -> instruction.isConstString("C")));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().m();
+      new B().m();
+      if (alwaysFalse()) {
+        new C().m();
+      }
+    }
+
+    static boolean alwaysFalse() {
+      return false;
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    void m() {
+      System.out.println("A");
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    void m() {
+      System.out.println("B");
+    }
+  }
+
+  @NeverClassInline
+  static class C {
+
+    void m() {
+      System.out.println("C");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
index f489112..209f9f9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -48,6 +49,7 @@
             .addInnerClasses(InterfaceMethodTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .enableNoVerticalClassMergingAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
             .run(TestClass.class)
@@ -84,6 +86,7 @@
     Uninstantiated m();
   }
 
+  @NeverClassInline
   static class A implements I {
 
     @NeverInline
@@ -97,6 +100,7 @@
   // The purpose of this class is merely to avoid that the invoke-interface instruction in
   // TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method
   // I.m() would not be present in the output.
+  @NeverClassInline
   @NoHorizontalClassMerging
   static class B implements I {
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index 7890640..3cbaed0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -49,6 +50,7 @@
             .addInnerClasses(NestedInterfaceMethodTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
             .enableNoVerticalClassMergingAnnotations()
             .addOptionsModification(
                 options -> {
@@ -91,6 +93,7 @@
   @NoVerticalClassMerging
   interface J extends I {}
 
+  @NeverClassInline
   static class A implements J {
 
     @Override
@@ -100,11 +103,13 @@
     }
   }
 
+  @NeverClassInline
   static class B extends A {}
 
   // The purpose of this class is merely to avoid that the invoke-interface instruction in
   // TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method
   // I.m() would not be present in the output.
+  @NeverClassInline
   static class C extends A {}
 
   static class Uninstantiated {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
index 88459a5..a04df37 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/SynchronizedMethodTest.java
@@ -4,11 +4,17 @@
 
 package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -18,23 +24,41 @@
 import java.util.List;
 import java.util.stream.Collectors;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class SynchronizedMethodTest extends TestBase {
 
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SynchronizedMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test() throws Exception {
     String expectedOutput = StringUtils.lines("In A.m()", "Got NullPointerException");
     CodeInspector inspector =
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addInnerClasses(SynchronizedMethodTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .run(TestClass.class)
+            .enableNeverClassInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
     ClassSubject clazz = inspector.clazz(A.class);
     MethodSubject method = clazz.uniqueMethodWithName("m");
+    assertThat(method, isPresent());
 
     // The invoke on the uninstantiated turns into a "throw null", and the synchronized method
     // already have a throw instruction in the catch all handler ensuring monitor exit is called.
@@ -43,24 +67,30 @@
             .streamInstructions()
             .filter(InstructionSubject::isThrow)
             .collect(Collectors.toList());
-    assertEquals(2, throwInstructions.size());
+    assertEquals(1 + BooleanUtils.intValue(parameters.isDexRuntime()), throwInstructions.size());
 
-    // The inserted "throw null" should still be covered by the catch all to ensure monitor exit
-    // is called.
-    List<InstructionSubject> catchAllCoveredInstructions = new ArrayList<>();
-    method.iterateTryCatches().forEachRemaining(tryCatchSubject -> {
-      if (tryCatchSubject.hasCatchAll()) {
-        catchAllCoveredInstructions.addAll(
-        throwInstructions
-            .stream()
-            .filter(
-                throwInstruction ->
-                    tryCatchSubject.getRange().includes(throwInstruction.getOffset(method)))
-            .collect(Collectors.toList()));
-      }
-    });
-    assertEquals(1, catchAllCoveredInstructions.size());
-    assertSame(throwInstructions.get(0), catchAllCoveredInstructions.get(0));
+    if (parameters.isDexRuntime()) {
+      // The inserted "throw null" should still be covered by the catch all to ensure monitor exit
+      // is called.
+      List<InstructionSubject> catchAllCoveredInstructions = new ArrayList<>();
+      method
+          .iterateTryCatches()
+          .forEachRemaining(
+              tryCatchSubject -> {
+                if (tryCatchSubject.hasCatchAll()) {
+                  catchAllCoveredInstructions.addAll(
+                      throwInstructions.stream()
+                          .filter(
+                              throwInstruction ->
+                                  tryCatchSubject
+                                      .getRange()
+                                      .includes(throwInstruction.getOffset(method)))
+                          .collect(Collectors.toList()));
+                }
+              });
+      assertEquals(1, catchAllCoveredInstructions.size());
+      assertSame(throwInstructions.get(0), catchAllCoveredInstructions.get(0));
+    }
   }
 
   static class TestClass {
@@ -79,6 +109,7 @@
     }
   }
 
+  @NeverClassInline
   static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
index ef269dc..49b90e7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
@@ -32,7 +32,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(TestParameters parameters) {
@@ -72,17 +72,20 @@
             dumpAnnotation2(),
             dumpAnnotation3())
         .addKeepMainRule("Test")
-        .addKeepRules("-keep @interface Annotation?")
-        .addKeepAttributes("RuntimeVisibleParameterAnnotations")
+        .addKeepRules(
+            "-keep @interface Annotation?",
+            "-neverclassinline class *",
+            "-nohorizontalclassmerging class Test$Inner?")
+        .addKeepRuntimeVisibleParameterAnnotations()
         .enableProguardTestOptions()
-        .addKeepRules("-neverclassinline class *")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(inspector -> {
-          checkClass(inspector.clazz("Test$Inner1"), "Annotation1");
-          checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
-          checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
-        })
+        .inspect(
+            inspector -> {
+              checkClass(inspector.clazz("Test$Inner1"), "Annotation1");
+              checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
+              checkClass(inspector.clazz("Test$Inner2"), "Annotation2");
+            })
         .run(parameters.getRuntime(), "Test")
         .assertSuccessWithOutput(expectedOutput);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index 52131b3..768fbd6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -40,6 +41,7 @@
         .addInnerClasses(UnusedInterfaceWithDefaultMethodTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -94,5 +96,6 @@
     }
   }
 
+  @NeverClassInline
   static class A implements J {}
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 16413e0..f6b24da 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -20,12 +21,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -63,15 +63,6 @@
         && clazz.getInterfaces().size() == 1;
   }
 
-  private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) {
-    Set<DexType> lambdaClasses =
-        inspector.allClasses().stream()
-            .filter(clazz -> isLambda(clazz.getDexProgramClass()))
-            .map(clazz -> clazz.getDexProgramClass().type)
-            .collect(Collectors.toSet());
-    return lambdaClasses::contains;
-  }
-
   @Test
   public void testJStyleLambdas() throws Exception {
     String mainClassName = "class_inliner_lambda_j_style.MainKt";
@@ -116,10 +107,10 @@
                     .addKeepRules("-neverinline class * { void test*State*(...); }"))
         .inspect(
             inspector -> {
-              // TODO(b/173337498): MainKt$testStateless$1 should be class inlined.
+              // TODO(b/173337498): MainKt$testStateless$1 should always be class inlined.
               assertThat(
                   inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
-                  isPresent());
+                  notIf(isPresent(), testParameters.isDexRuntime()));
 
               // TODO(b/173337498): MainKt$testStateful$1 should be class inlined.
               assertThat(
@@ -209,10 +200,8 @@
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(mainClassName);
-
-              // TODO(b/141719453): Data class should maybe be class inlined.
               assertEquals(
-                  Sets.newHashSet("class_inliner_data_class.Alpha"),
+                  Collections.emptySet(),
                   collectAccessedTypes(
                       type -> !type.toSourceString().startsWith("java."),
                       clazz,
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index d3d99ee..2dd90c1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -120,7 +120,7 @@
     testForD8()
         .setMinApi(AndroidApiLevel.K)
         .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class))
-        .addMainDexListClasses(TestClass.class)
+        .addMainDexKeepClassAndMemberRules(TestClass.class)
         .setMainDexListConsumer(consumer)
         .compile();
     assertTrue(consumer.called);
@@ -141,7 +141,7 @@
     testForD8()
         .setMinApi(AndroidApiLevel.K)
         .addProgramFiles(dexOutput)
-        .addMainDexKeepClassRules(TestClass.class)
+        .addMainDexKeepClassAndMemberRules(TestClass.class)
         .setMainDexListConsumer(consumer)
         .compile();
     assertTrue(consumer.called);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index 5e88d7d..8252f4d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -8,11 +8,14 @@
 
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Files;
@@ -26,7 +29,7 @@
 
   static final AndroidApiLevel nativeMultiDexLevel = AndroidApiLevel.L;
 
-  static final String EXPECTED = StringUtils.lines("AB");
+  static final String EXPECTED = StringUtils.lines("A");
 
   private final TestParameters parameters;
 
@@ -53,8 +56,8 @@
       D8TestCompileResult compileResult =
           testForD8()
               .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
-              .addMainDexListClasses(TestClass.class, A.class)
-              .setMinApiThreshold(parameters.getApiLevel())
+              .addMainDexKeepClassAndMemberRules(TestClass.class)
+              .setMinApi(parameters.getApiLevel())
               .compile();
       checkCompilationResult(compileResult);
     }
@@ -66,19 +69,67 @@
     D8TestCompileResult intermediateResult =
         testForD8()
             .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
-            .setMinApiThreshold(parameters.getApiLevel())
+            .setMinApi(parameters.getApiLevel())
             .setIntermediate(true)
             .compile();
     D8TestCompileResult compileResult =
         testForD8()
             .addProgramFiles(intermediateResult.writeToZip())
-            .addMainDexKeepClassRules(TestClass.class, A.class)
-            .setMinApiThreshold(parameters.getApiLevel())
+            .addMainDexKeepClassAndMemberRules(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
             .compile();
     checkCompilationResult(compileResult);
   }
 
+  /**
+   * This test checks for maintained support of including synthetics from main-dex-list entries in
+   * the main-dex file. This test simulates that the tracing done at the class-file level has
+   * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
+   * included in the main-dex file.
+   *
+   * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+   */
+  @Test
+  public void testDeprecatedSyntheticsFromMainDexListD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    D8TestCompileResult compileResult =
+        testForD8()
+            .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+            .addMainDexListClasses(TestClass.class, A.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    checkCompilationResult(compileResult);
+  }
+
+  /**
+   * This test checks for maintained support of including synthetics from main-dex-list entries in
+   * the main-dex file. This test simulates that the tracing done at the class-file level has
+   * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
+   * included in the main-dex file.
+   *
+   * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+   */
+  @Test
+  public void testDeprecatedSyntheticsFromMainDexListR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(o -> o.minimalMainDex = true)
+            .addMainDexListClasses(TestClass.class, A.class)
+            .noMinification()
+            .noTreeShaking()
+            .compile();
+    checkCompilationResult(compileResult, compileResult.app);
+  }
+
   private void checkCompilationResult(D8TestCompileResult compileResult) throws Exception {
+    checkCompilationResult(compileResult, compileResult.app);
+  }
+
+  private void checkCompilationResult(TestCompileResult compileResult, AndroidApp app)
+      throws Exception {
     if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
         < nativeMultiDexLevel.getLevel()) {
       compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
@@ -86,7 +137,7 @@
       compileResult.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED);
     }
     Path out = temp.newFolder().toPath();
-    compileResult.apply(b -> b.app.writeToDirectory(out, OutputMode.DexIndexed));
+    app.writeToDirectory(out, OutputMode.DexIndexed);
     Path classes = out.resolve("classes.dex");
     Path classes2 = out.resolve("classes2.dex");
     assertTrue(Files.exists(classes));
@@ -119,7 +170,7 @@
 
   static class A {
     Getter foo() {
-      return () -> "A" + new B().foo().get();
+      return () -> "A";
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
index fb89853..3727423 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -338,6 +338,7 @@
 
   static class B extends A {
 
+    @Override
     public void method() {
       System.out.println("In B.method()");
       super.method();
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 6a01e83..85c8459 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -11,50 +12,51 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider.Visitor;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.DataResourceConsumerForTesting;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.KeepingDiagnosticHandler;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.io.File;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import org.junit.Before;
 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 AdaptResourceFileNamesTest extends ProguardCompatibilityTestBase {
 
-  private Backend backend;
+  private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public AdaptResourceFileNamesTest(Backend backend) {
-    this.backend = backend;
+  public AdaptResourceFileNamesTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   private static final Path CF_DIR =
@@ -63,15 +65,6 @@
       Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
           .resolve("adaptresourcefilenames" + FileUtils.JAR_EXTENSION);
 
-  private KeepingDiagnosticHandler diagnosticsHandler;
-  private ClassNameMapper mapper = null;
-
-  @Before
-  public void reset() {
-    diagnosticsHandler = new KeepingDiagnosticHandler();
-    mapper = null;
-  }
-
   private static String getProguardConfig(
       boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
     String adaptResourceFilenamesRule;
@@ -86,7 +79,6 @@
     return String.join(
         System.lineSeparator(),
         adaptResourceFilenamesRule,
-        "-keeppackagenames adaptresourcefilenames**",
         "-keep class adaptresourcefilenames.TestClass {",
         "  public static void main(...);",
         "}");
@@ -114,30 +106,38 @@
   }
 
   @Test
-  public void testEnabled() throws Exception {
+  public void testEnabled() throws Throwable {
     DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, null),
         dataResourceConsumer,
-        ToolHelper.consumeString(this::checkR8Renamings));
-    // Check that the generated resources have the expected names.
-    for (DataEntryResource dataResource : getOriginalDataResources()) {
-      assertNotNull(
-          "Resource not renamed as expected: " + dataResource.getName(),
-          dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)));
-    }
+        R8TestBuilder::enableProguardTestOptions,
+        mapper -> {
+          checkR8Renamings(mapper);
+          // Check that the generated resources have the expected names.
+          for (DataEntryResource dataResource : getOriginalDataResources()) {
+            ImmutableList<String> object =
+                dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
+            if (object == null) {
+              object =
+                  dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
+            }
+            assertNotNull("Resource not renamed as expected: " + dataResource.getName(), object);
+          }
+        });
   }
 
   @Test
-  public void testEnabledWithFilter() throws Exception {
+  public void testEnabledWithFilter() throws Throwable {
     DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
     compileWithR8(
         getProguardConfigWithNeverInline(true, "**.md"),
         dataResourceConsumer,
-        ToolHelper.consumeString(this::checkR8Renamings));
+        R8TestBuilder::enableProguardTestOptions,
+        this::checkR8Renamings);
     // Check that the generated resources have the expected names.
     Map<String, String> expectedRenamings =
-        ImmutableMap.of("adaptresourcefilenames/B.md", "adaptresourcefilenames/b.md");
+        ImmutableMap.of("adaptresourcefilenames/B.md", "a/b.md");
     for (DataEntryResource dataResource : getOriginalDataResources()) {
       assertNotNull(
           "Resource not renamed as expected: " + dataResource.getName(),
@@ -147,9 +147,12 @@
   }
 
   @Test
-  public void testDisabled() throws Exception {
+  public void testDisabled() throws Throwable {
     DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
-    compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
+    compileWithR8(
+        getProguardConfigWithNeverInline(false, null),
+        dataResourceConsumer,
+        R8TestBuilder::enableProguardTestOptions);
     // Check that none of the resources were renamed.
     for (DataEntryResource dataResource : getOriginalDataResources()) {
       assertNotNull(
@@ -159,22 +162,25 @@
   }
 
   @Test
-  public void testCollisionBehavior() throws Exception {
+  public void testCollisionBehavior() throws Throwable {
     DataResourceConsumerForTesting dataResourceConsumer = new DataResourceConsumerForTesting();
-    compileWithR8(
-        getProguardConfigWithNeverInline(true, null),
-        dataResourceConsumer,
-        ToolHelper.consumeString(this::checkR8Renamings),
-        ImmutableList.<DataEntryResource>builder()
-            .addAll(getOriginalDataResources())
-            .add(
-                DataEntryResource.fromBytes(
-                    new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
-            .build());
-    assertEquals(1, diagnosticsHandler.warnings.size());
-    assertThat(
-        diagnosticsHandler.warnings.get(0).getDiagnosticMessage(),
-        containsString("Resource 'adaptresourcefilenames/b.txt' already exists."));
+    R8TestCompileResult compileResult =
+        compileWithR8(
+            getProguardConfigWithNeverInline(true, null),
+            dataResourceConsumer,
+            builder -> {
+              builder.enableProguardTestOptions();
+              builder.allowDiagnosticWarningMessages();
+            },
+            this::checkR8Renamings,
+            ImmutableList.<DataEntryResource>builder()
+                .addAll(getOriginalDataResources())
+                .add(DataEntryResource.fromBytes(new byte[0], "a/b.txt", Origin.unknown()))
+                .build());
+    compileResult.inspectDiagnosticMessages(
+        diagnosticMessages ->
+            diagnosticMessages.assertWarningsMatch(
+                diagnosticMessage(containsString("Resource 'a/b.txt' already exists."))));
     assertEquals(getOriginalDataResources().size(), dataResourceConsumer.size());
   }
 
@@ -243,64 +249,59 @@
             .count());
   }
 
-  private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
-      throws CompilationFailedException, IOException {
-    return compileWithR8(proguardConfig, dataResourceConsumer, null);
-  }
-
-  private AndroidApp compileWithR8(
+  private void compileWithR8(
       String proguardConfig,
       DataResourceConsumer dataResourceConsumer,
-      StringConsumer proguardMapConsumer)
-      throws CompilationFailedException, IOException {
-    return compileWithR8(
-        proguardConfig, dataResourceConsumer, proguardMapConsumer, getOriginalDataResources());
+      ThrowableConsumer<R8FullTestBuilder> builderConsumer)
+      throws Throwable {
+    compileWithR8(proguardConfig, dataResourceConsumer, builderConsumer, null);
   }
 
-  private AndroidApp compileWithR8(
+  private void compileWithR8(
       String proguardConfig,
       DataResourceConsumer dataResourceConsumer,
-      StringConsumer proguardMapConsumer,
+      ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+      Consumer<ClassNameMapper> proguardMapConsumer)
+      throws Throwable {
+    compileWithR8(
+        proguardConfig,
+        dataResourceConsumer,
+        builderConsumer,
+        proguardMapConsumer,
+        getOriginalDataResources());
+  }
+
+  private R8TestCompileResult compileWithR8(
+      String proguardConfig,
+      DataResourceConsumer dataResourceConsumer,
+      ThrowableConsumer<R8FullTestBuilder> builderConsumer,
+      Consumer<ClassNameMapper> proguardMapConsumer,
       List<DataEntryResource> dataResources)
-      throws CompilationFailedException, IOException {
-    R8Command command =
-        ToolHelper.allowTestProguardOptions(
-                ToolHelper.prepareR8CommandBuilder(
-                        getAndroidApp(dataResources), emptyConsumer(backend), diagnosticsHandler)
-                    .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
-            .addLibraryFiles(runtimeJar(backend))
-            .build();
-    return ToolHelper.runR8(
-        command,
-        options -> {
-          options.dataResourceConsumer = dataResourceConsumer;
-          options.proguardMapConsumer = proguardMapConsumer;
-        });
-  }
-
-  private void checkR8Renamings(String proguardMap) {
-    try {
-      // Check that the renamings are as expected. These exact renamings are not important as
-      // such, but the test expectations rely on them.
-      mapper = ClassNameMapper.mapperFromString(proguardMap);
-      assertEquals(
-          "adaptresourcefilenames.TestClass",
-          mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
-      assertEquals(
-          "adaptresourcefilenames.B", mapper.deobfuscateClassName("adaptresourcefilenames.b"));
-      assertEquals(
-          "adaptresourcefilenames.B$Inner",
-          mapper.deobfuscateClassName("adaptresourcefilenames.a"));
-    } catch (IOException e) {
-      throw new RuntimeException(e);
+      throws Throwable {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR))
+            .addDataResources(dataResources)
+            .addKeepRules(proguardConfig)
+            .apply(builderConsumer)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(options -> options.dataResourceConsumer = dataResourceConsumer)
+            .compile();
+    if (proguardMapConsumer != null) {
+      compile.inspectProguardMap(
+          map -> proguardMapConsumer.accept(ClassNameMapper.mapperFromString(map)));
     }
+    return compile;
   }
 
-  private AndroidApp getAndroidApp(List<DataEntryResource> dataResources) throws IOException {
-    AndroidApp.Builder builder = AndroidApp.builder();
-    builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR));
-    dataResources.forEach(builder::addDataResource);
-    return builder.build();
+  private void checkR8Renamings(ClassNameMapper mapper) {
+    // Check that the renamings are as expected. These exact renamings are not important as
+    // such, but the test expectations rely on them.
+    assertEquals(
+        "adaptresourcefilenames.TestClass",
+        mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
+    assertEquals("adaptresourcefilenames.B", mapper.deobfuscateClassName("a.b"));
+    assertEquals("adaptresourcefilenames.B$Inner", mapper.deobfuscateClassName("a.a"));
   }
 
   private static List<DataEntryResource> getOriginalDataResources() {
diff --git a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
index d9b9a3c..218e037 100644
--- a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
@@ -69,11 +69,6 @@
             options -> {
               options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
               options.enableClassInlining = false;
-
-              // TODO(b/179019716): Add support for merging in presence of annotations.
-              options.horizontalClassMergerOptions()
-                      .skipNoClassesOrMembersWithAnnotationsPolicyForTesting =
-                  true;
             })
         .addHorizontallyMergedClassesInspector(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/regress/Regress181837660.java b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
new file mode 100644
index 0000000..7fd9e77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dexsplitter.SplitterTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * We need to ensure that we distribute the synthetic items in the features where they where
+ * generated.
+ */
+@RunWith(Parameterized.class)
+public class Regress181837660 extends SplitterTestBase {
+
+  public static final String EXPECTED = StringUtils.lines("42");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public Regress181837660(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testDistribution() throws Exception {
+    ProcessResult processResult =
+        testR8Splitter(
+            parameters,
+            ImmutableSet.of(BaseClass.class),
+            ImmutableSet.of(FeatureClass.class),
+            FeatureClass.class,
+            b -> {},
+            this::configure);
+
+    assertEquals(1, processResult.exitCode);
+    // We can't actually read the field since it is in the feature.
+    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  @Test
+  public void testRegress181571571() throws Exception {
+    ProcessResult processResult =
+        testR8Splitter(
+            parameters,
+            ImmutableSet.of(BaseClass.class),
+            ImmutableSet.of(FeatureClass.class),
+            FeatureClass.class,
+            b -> {},
+            this::configureNoInlineAnnotations);
+    // TODO(b/181571571): This should not succeed as illustrated by non inlining case
+    assertEquals(0, processResult.exitCode);
+    // We can't actually read the field since it is in the feature.
+    assertFalse(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException {
+    testBuilder.enableInliningAnnotations().noMinification().setMinApi(parameters.getApiLevel());
+  }
+
+  private void configureNoInlineAnnotations(R8FullTestBuilder testBuilder)
+      throws NoSuchMethodException {
+    testBuilder.noMinification().setMinApi(parameters.getApiLevel());
+  }
+
+  public static class BaseClass {
+    @NeverInline
+    public static String getFromFeature() {
+      return FeatureClass.featureString;
+    }
+  }
+
+  public static class FeatureClass implements RunInterface {
+
+    public static String featureString = "22";
+
+    public static String getAString() {
+      return BaseClass.getFromFeature();
+    }
+
+    @Override
+    public void run() {
+      System.out.println(getAString());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
index 16d9503..0a1f760 100644
--- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdaReceiverTest.java
@@ -7,7 +7,8 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -16,33 +17,34 @@
 @RunWith(Parameterized.class)
 public class InstantiatedLambdaReceiverTest extends TestBase {
 
-  private Backend backend;
-
   private static final String expectedOutput = "In C.m()";
+  private final TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
-  public InstantiatedLambdaReceiverTest(Backend backend) {
-    this.backend = backend;
+  public InstantiatedLambdaReceiverTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
   public void jvmTest() throws Exception {
-    assumeTrue(
-        "JVM test independent of Art version - only run when testing on latest",
-        ToolHelper.getDexVm().getVersion().isLatest());
-    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
   }
 
   @Test
-  public void dexTest() throws Exception {
-    testForR8(backend)
+  public void r8Test() throws Exception {
+    testForR8(parameters.getBackend())
         .addInnerClasses(InstantiatedLambdaReceiverTest.class)
         .addKeepMainRule(TestClass.class)
-        .run(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 6f6ad5a..f98e5ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -44,25 +44,21 @@
 public class NonVirtualOverrideTest extends TestBase {
 
   private final TestParameters parameters;
-  private final boolean enableClassInlining;
   private final boolean enableVerticalClassMerging;
 
   static class Dimensions {
 
     private final Backend backend;
-    private final boolean enableClassInlining;
     private final boolean enableVerticalClassMerging;
 
-    public Dimensions(
-        Backend backend, boolean enableClassInlining, boolean enableVerticalClassMerging) {
+    public Dimensions(Backend backend, boolean enableVerticalClassMerging) {
       this.backend = backend;
-      this.enableClassInlining = enableClassInlining;
       this.enableVerticalClassMerging = enableVerticalClassMerging;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(backend, enableClassInlining, enableVerticalClassMerging);
+      return Objects.hash(backend, enableVerticalClassMerging);
     }
 
     @Override
@@ -72,23 +68,19 @@
       }
       Dimensions other = (Dimensions) o;
       return this.backend == other.backend
-          && this.enableClassInlining == other.enableClassInlining
           && this.enableVerticalClassMerging == other.enableVerticalClassMerging;
     }
   }
 
-  @Parameterized.Parameters(name = "Backend: {0}, class inlining: {1}, vertical class merging: {2}")
+  @Parameterized.Parameters(name = "{0}, vertical class merging: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().build(),
-        BooleanUtils.values(),
         BooleanUtils.values());
   }
 
-  public NonVirtualOverrideTest(
-      TestParameters parameters, boolean enableClassInlining, boolean enableVerticalClassMerging) {
+  public NonVirtualOverrideTest(TestParameters parameters, boolean enableVerticalClassMerging) {
     this.parameters = parameters;
-    this.enableClassInlining = enableClassInlining;
     this.enableVerticalClassMerging = enableVerticalClassMerging;
   }
 
@@ -162,7 +154,6 @@
         .addKeepMainRule(NonVirtualOverrideTestClass.class)
         .addOptionsModification(
             options -> {
-              options.enableClassInlining = dimensions.enableClassInlining;
               options.enableVerticalClassMerging = dimensions.enableVerticalClassMerging;
               options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
             })
@@ -173,18 +164,16 @@
   @Test
   public void test() throws Exception {
     // Run the program on Art after is has been compiled with R8.
-    String referenceResult =
-        expectedResults.apply(!enableClassInlining && isDexVmBetween5_1_1and7_0_0(parameters));
+    String referenceResult = expectedResults.apply(isDexVmBetween5_1_1and7_0_0(parameters));
     R8TestCompileResult compiled =
         compilationResults.apply(
-            new Dimensions(
-                parameters.getBackend(), enableClassInlining, enableVerticalClassMerging));
+            new Dimensions(parameters.getBackend(), enableVerticalClassMerging));
     compiled
         .run(parameters.getRuntime(), NonVirtualOverrideTestClass.class)
         .assertSuccessWithOutput(referenceResult);
 
     // Check that B is present and that it doesn't contain the unused private method m2.
-    if (!enableClassInlining && !enableVerticalClassMerging) {
+    if (!enableVerticalClassMerging) {
       CodeInspector inspector = compiled.inspector();
       ClassSubject classSubject = inspector.clazz(B.class.getName());
       assertThat(classSubject, isPresentAndRenamed());
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
index ace926f..9246164 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking.annotations;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -55,6 +56,7 @@
         .addKeepMainRule(TestClass.class)
         .addKeepRuntimeVisibleAnnotations()
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noMinification()
@@ -94,6 +96,7 @@
     void targetedMethod();
   }
 
+  @NeverClassInline
   @NoHorizontalClassMerging
   static class InterfaceImpl implements Interface {
 
@@ -104,6 +107,7 @@
     }
   }
 
+  @NeverClassInline
   @NoHorizontalClassMerging
   static class OtherInterfaceImpl implements Interface {
 
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
index 5bb8e48..035ddf7 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.references.Reference.methodFromMethod;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -31,6 +32,7 @@
     }
   }
 
+  @NeverClassInline
   @NoVerticalClassMerging
   public static class B extends A {
     // Intermediate to A.
@@ -72,6 +74,7 @@
     GraphInspector inspector =
         testForR8(parameters.getBackend())
             .enableGraphInspector()
+            .enableNeverClassInliningAnnotations()
             .enableNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 6f3a6cc..46924fd 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -90,6 +90,15 @@
     return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT);
   }
 
+  public static boolean isHorizontalInitializerTypeArgument(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(
+            reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
+        || SyntheticNaming.isSynthetic(
+            reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
+        || SyntheticNaming.isSynthetic(
+            reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3);
+  }
+
   public static Matcher<String> containsInternalSyntheticReference() {
     return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
   }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index f828cfc..0169604 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -517,6 +517,16 @@
     static MethodPredicate onName(String name) {
       return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName);
     }
+
+    static boolean testContext(MethodPredicate predicate, MethodContext context) {
+      MethodReference reference = context.getReference();
+      return predicate.test(
+          context.accessFlags,
+          reference.getMethodName(),
+          reference.getMethodDescriptor(),
+          null,
+          null);
+    }
   }
 
   @FunctionalInterface
@@ -970,4 +980,16 @@
           }
         });
   }
+
+  public ClassFileTransformer setMaxStackHeight(MethodPredicate predicate, int newMaxStack) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitMaxs(int maxStack, int maxLocals) {
+            super.visitMaxs(
+                MethodPredicate.testContext(predicate, getContext()) ? newMaxStack : maxStack,
+                maxLocals);
+          }
+        });
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
index c35bbf2..f681673 100644
--- a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -42,6 +42,9 @@
 
   private Path getOutputPath(GenerateMainDexListCommand command) {
     StringConsumer consumer = command.getMainDexListConsumer();
+    if (consumer instanceof JoiningStringConsumer) {
+      consumer = ((JoiningStringConsumer) consumer).getConsumer();
+    }
     if (consumer instanceof StringConsumer.FileConsumer) {
       return ((StringConsumer.FileConsumer) consumer).getOutputPath();
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 959a2c9..e8818b2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -63,6 +63,10 @@
 
   boolean isConstString(String value, JumboStringMode jumboStringMode);
 
+  default boolean isConstString(String value) {
+    return isConstString(value, JumboStringMode.ALLOW);
+  }
+
   boolean isJumboString();
 
   long getConstNumber();
diff --git a/third_party/internal-apps/youtube_15_33.tar.gz.sha1 b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
new file mode 100644
index 0000000..ad38c2b
--- /dev/null
+++ b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
@@ -0,0 +1 @@
+a992d1cdf6b957a6e4b06361ea1b8edbaecc3088
\ No newline at end of file
diff --git a/tools/adb.py b/tools/adb.py
index c6cece7..07ca034 100644
--- a/tools/adb.py
+++ b/tools/adb.py
@@ -20,6 +20,8 @@
     ['adb', '-s', emulator_id, 'uninstall', app_id],
     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   stdout, stderr = process.communicate()
+  stdout = stdout.decode('UTF-8')
+  stderr = stderr.decode('UTF-8')
 
   if stdout.strip() == 'Success':
     # Successfully uninstalled
@@ -40,7 +42,7 @@
 
 
 def wait_for_emulator(emulator_id):
-  stdout = subprocess.check_output(['adb', 'devices'])
+  stdout = subprocess.check_output(['adb', 'devices']).decode('UTF-8')
   if '{}\tdevice'.format(emulator_id) in stdout:
     return True
 
@@ -51,7 +53,7 @@
   while True:
     time.sleep(10)
     time_waited += 10
-    stdout = subprocess.check_output(['adb', 'devices'])
+    stdout = subprocess.check_output(['adb', 'devices']).decode('UTF-8')
     if '{}\tdevice'.format(emulator_id) not in stdout:
       print('... still waiting for connection')
       if time_waited >= 5 * 60:
diff --git a/tools/compiledump.py b/tools/compiledump.py
index b5f49d1..3828739 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -147,10 +147,13 @@
     if self.if_exists('proguard_input.config'):
       print("Unimplemented: proguard_input configuration.")
 
-  def main_dex_resource(self):
+  def main_dex_list_resource(self):
     if self.if_exists('main-dex-list.txt'):
       print("Unimplemented: main-dex-list.")
 
+  def main_dex_rules_resource(self):
+    return self.if_exists('main-dex-rules.txt')
+
   def build_properties_file(self):
     return self.if_exists('build.properties')
 
@@ -318,6 +321,8 @@
       if hasattr(args, 'config_file_consumer') and args.config_file_consumer:
         args.config_file_consumer(dump.config_file())
       cmd.extend(['--pg-conf', dump.config_file()])
+    if dump.main_dex_rules_resource():
+      cmd.extend(['--main-dex-rules', dump.main_dex_rules_resource()])
     if compiler != 'd8':
       cmd.extend(['--pg-map-output', '%s.map' % out])
     if min_api:
@@ -332,10 +337,9 @@
       print(subprocess.check_output(cmd, stderr=subprocess.STDOUT))
       return 0
     except subprocess.CalledProcessError as e:
-      print(e.output)
       if not args.nolib and version != 'source':
         stacktrace = os.path.join(temp, 'stacktrace')
-        open(stacktrace, 'w+').write(e.output)
+        open(stacktrace, 'w+').write(e.output.decode('UTF-8'))
         local_map = utils.R8LIB_MAP if version == 'master' else None
         hash_or_version = None if version == 'master' else version
         print("=" * 80)
@@ -343,6 +347,8 @@
         print("=" * 80)
         retrace.run(
           local_map, hash_or_version, stacktrace, is_hash(version), no_r8lib=False)
+      else:
+        print(e.output.decode('UTF-8'))
       return 1
 
 def run(args, otherargs):
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index c4de6e3..1644895 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -8,6 +8,7 @@
 import as_utils
 import compiledump
 import gradle
+import hashlib
 import optparse
 import os
 import shutil
@@ -50,6 +51,7 @@
       'folder': None,
       'skip_recompilation': False,
       'compiler_properties': [],
+      'internal': False,
     }
     # This below does not work in python3
     defaults.update(fields.items())
@@ -340,8 +342,6 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/crane',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
   # TODO(b/173167253): Check if monkey testing works.
   App({
@@ -353,8 +353,6 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/jetcaster',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
   # TODO(b/173167253): Check if monkey testing works.
   App({
@@ -366,8 +364,6 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/jetchat',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
   # TODO(b/173167253): Check if monkey testing works.
   App({
@@ -379,8 +375,6 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/jetnews',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
   # TODO(b/173167253): Check if monkey testing works.
   App({
@@ -392,8 +386,6 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/jetsnack',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
   # TODO(b/173167253): Check if monkey testing works.
   App({
@@ -405,8 +397,6 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/jetsurvey',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
   # TODO(b/173167253): Check if monkey testing works.
   App({
@@ -418,8 +408,6 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/owl',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
   # TODO(b/173167253): Check if monkey testing works.
   App({
@@ -431,9 +419,17 @@
     'url': 'https://github.com/android/compose-samples',
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/rally',
-    # TODO(b/173176042): Fix recompilation
-    'skip_recompilation': True,
   }),
+  App({
+    'id': 'youtube_15_33',
+    'name': 'youtube_15_33',
+    'dump_app': 'dump.zip',
+    'apk_app': 'YouTubeRelease_unsigned.apk',
+    'folder': 'youtube_15_33',
+    'internal': True,
+    # TODO(b/181629268): Fix recompilation
+    'skip_recompilation': True,
+  })
 ]
 
 
@@ -453,8 +449,11 @@
         f.write(line)
 
 
-def download_app(app_sha):
-  utils.DownloadFromGoogleCloudStorage(app_sha)
+def download_app(app_sha, internal, quiet=False):
+  if internal:
+    utils.DownloadFromX20(app_sha)
+  else:
+    utils.DownloadFromGoogleCloudStorage(app_sha, quiet=quiet)
 
 
 def is_logging_enabled_for(app, options):
@@ -503,11 +502,12 @@
 
 def get_results_for_app(app, options, temp_dir):
   app_folder = app.folder if app.folder else app.name + "_" + app.revision
-  app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
+  app_dir = (os.path.join(utils.INTERNAL_DUMPS_DIR, app_folder) if app.internal
+              else os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder))
 
   if not os.path.exists(app_dir) and not options.golem:
     # Download the app from google storage.
-    download_app(app_dir + ".tar.gz.sha1")
+    download_app(app_dir + ".tar.gz.sha1", app.internal)
 
   # Ensure that the dumps are in place
   assert os.path.isfile(dump_for_app(app_dir, app)), "Could not find dump " \
@@ -789,6 +789,11 @@
         warn('  {}-#{}: {}'.format(shrinker, compilation_index, build_status))
         continue
 
+      if options.golem:
+        print('%s(RunTimeRaw): %s ms' % (app.name, result.get('duration')))
+        print('%s(CodeSize): %s' % (app.name, result.get('dex_size')))
+        continue
+
       print('  {}-#{}:'.format(shrinker, compilation_index))
       dex_size = result.get('dex_size')
       msg = '    dex size: {}'.format(dex_size)
@@ -839,10 +844,17 @@
                     help='What app collection to run',
                     choices=[collection.name for collection in APP_COLLECTIONS],
                     action='append')
+  result.add_option('--app-logging-filter', '--app_logging_filter',
+                    help='The apps for which to turn on logging',
+                    action='append')
   result.add_option('--bot',
                     help='Running on bot, use third_party dependency.',
                     default=False,
                     action='store_true')
+  result.add_option('--generate-golem-config', '--generate_golem_config',
+                    help='Generate a new config for golem.',
+                    default=False,
+                    action='store_true')
   result.add_option('--debug-agent',
                     help='Enable Java debug agent and suspend compilation '
                          '(default disabled)',
@@ -861,15 +873,16 @@
                     action='store_true')
   result.add_option('--hash',
                     help='The commit of R8 to use')
+  result.add_option('--internal',
+                    help='Run internal apps if set, otherwise run opensource',
+                    default=False,
+                    action='store_true')
   result.add_option('--keystore',
                     help='Path to app.keystore',
                     default=os.path.join(utils.TOOLS_DIR, 'debug.keystore'))
   result.add_option('--keystore-password', '--keystore_password',
                     help='Password for app.keystore',
                     default='android')
-  result.add_option('--app-logging-filter', '--app_logging_filter',
-                    help='The apps for which to turn on logging',
-                    action='append')
   result.add_option('--monkey',
                     help='Whether to install and run app(s) with monkey',
                     default=False,
@@ -934,7 +947,7 @@
     del options.app
     del options.app_collection
   else:
-    options.apps = APPS
+    options.apps = [app for app in APPS if app.internal == options.internal]
 
   if options.app_logging_filter:
     for app_name in options.app_logging_filter:
@@ -960,6 +973,65 @@
   return (options, args)
 
 
+def print_indented(s, indent):
+  print(' ' * indent + s)
+
+
+def get_sha256(gz_file):
+  with open(gz_file, 'rb') as f:
+    bytes = f.read() # read entire file as bytes
+    return hashlib.sha256(bytes).hexdigest();
+
+
+def get_sha_from_file(sha_file):
+  with open(sha_file, 'r') as f:
+    return f.readlines()[0]
+
+
+def print_golem_config(options):
+  print('// AUTOGENERATED FILE from tools/run_on_app_dump.py in R8 repo')
+  print('part of r8_config;')
+  print('')
+  print('final Suite dumpsSuite = new Suite("OpenSourceAppDumps");')
+  print('')
+  print('createOpenSourceAppBenchmarks() {')
+  print_indented('final cpus = ["Lenovo M90"];', 2)
+  for app in options.apps:
+    if app.folder and not app.internal:
+      indentation = 2;
+      print_indented('{', indentation)
+      indentation = 4
+      print_indented('final name = "%s";' % app.name, indentation)
+      print_indented('final benchmark =', indentation)
+      print_indented(
+          'new StandardBenchmark(name, [Metric.RunTimeRaw, Metric.CodeSize]);',
+          indentation + 4)
+      print_indented(
+          'final options = benchmark.addTargets(noImplementation, ["R8"]);',
+          indentation)
+      print_indented('options.cpus = cpus;', indentation)
+      print_indented('options.isScript = true;', indentation)
+      print_indented('options.fromRevision = 9700;', indentation);
+      print_indented('options.mainFile = "tools/run_on_app_dump.py "', indentation)
+      print_indented('"--golem --shrinker r8 --app %s";' % app.name, indentation + 4)
+      app_gz = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app.folder + '.tar.gz')
+      app_sha = app_gz + '.sha1'
+      # Golem uses a sha256 of the file in the cache, and you need to specify that.
+      download_app(app_sha, app.internal, quiet=True)
+      sha256 = get_sha256(app_gz)
+      sha = get_sha_from_file(app_sha)
+      print_indented('final resource = BenchmarkResource("",', indentation)
+      print_indented('type: BenchmarkResourceType.Storage,', indentation + 4)
+      print_indented('uri: "gs://r8-deps/%s",' % sha, indentation + 4)
+      print_indented('hash:', indentation + 4)
+      print_indented('"%s",' % sha256, indentation + 8)
+      print_indented('extract: "gz");', indentation + 4);
+      print_indented('options.resources.add(resource);', indentation)
+      print_indented('dumpsSuite.addBenchmark(name);', indentation)
+      indentation = 2
+      print_indented('}', indentation)
+  print('}')
+
 def main(argv):
   (options, args) = parse_options(argv)
 
@@ -969,13 +1041,16 @@
     print(options.shrinker)
 
   if options.golem:
-    golem.link_third_party()
     options.disable_assertions = True
     options.no_build = True
     options.r8_compilation_steps = 1
     options.quiet = True
     options.no_logging = True
 
+  if options.generate_golem_config:
+    print_golem_config(options)
+    return 0
+
   with utils.TempDir() as temp_dir:
     if options.hash:
       # Download r8-<hash>.jar from
diff --git a/tools/test.py b/tools/test.py
index 546a25f..9e2a3e2 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -192,6 +192,9 @@
     print('Building desugared library.')
     with utils.TempDir() as checkout_dir:
       archive_desugar_jdk_libs.CloneDesugaredLibrary('google', checkout_dir)
+      # Make sure bazel is extracted in third_party.
+      utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
+      utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
       (library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir)
       desugar_jdk_libs = os.path.join(desugar_jdk_libs_dir, os.path.basename(library_jar))
       shutil.copyfile(library_jar, desugar_jdk_libs)
diff --git a/tools/utils.py b/tools/utils.py
index 8b7acb7..1820991 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -79,6 +79,7 @@
 # TODO(b/152155164): Remove this when all apps has been migrated.
 OPENSOURCE_APPS_FOLDER = os.path.join(THIRD_PARTY, 'opensource_apps')
 OPENSOURCE_DUMPS_DIR = os.path.join(THIRD_PARTY, 'opensource-apps')
+INTERNAL_DUMPS_DIR = os.path.join(THIRD_PARTY, 'internal-apps')
 BAZEL_SHA_FILE = os.path.join(THIRD_PARTY, 'bazel.tar.gz.sha1')
 BAZEL_TOOL = os.path.join(THIRD_PARTY, 'bazel')
 JAVA8_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86.tar.gz.sha1')
@@ -237,15 +238,19 @@
   PrintCmd(cmd)
   subprocess.check_call(cmd)
 
-def DownloadFromGoogleCloudStorage(sha1_file, bucket='r8-deps', auth=False):
+def DownloadFromGoogleCloudStorage(sha1_file, bucket='r8-deps', auth=False,
+                                   quiet=False):
   suffix = '.bat' if IsWindows() else ''
   download_script = 'download_from_google_storage%s' % suffix
   cmd = [download_script]
   if not auth:
     cmd.append('-n')
   cmd.extend(['-b', bucket, '-u', '-s',  sha1_file])
-  PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  if not quiet:
+    PrintCmd(cmd)
+    subprocess.check_call(cmd)
+  else:
+    subprocess.check_output(cmd)
 
 def get_sha1(filename):
   sha1 = hashlib.sha1()