Merge commit '6ae4611db18f799a890c9de3a6e15cd6702ad90c' into dev-release
diff --git a/build.gradle b/build.gradle
index 870b615..e7aaa5d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -360,6 +360,7 @@
                 "android_jar/lib-v31",
                 "android_jar/lib-v32",
                 "android_jar/lib-v33",
+                "android_jar/lib-v34",
                 "android_jar/lib-master",
                 "api_database/api_database",
                 "api-outlining/simple-app-dump",
@@ -2298,6 +2299,12 @@
         systemProperty 'runtimes', project.property('runtimes')
     }
 
+    if (project.hasProperty('art_profile_rewriting_completeness_check')) {
+        String key = 'com.android.tools.r8.artprofilerewritingcompletenesscheck'
+        String value = project.property('art_profile_rewriting_completeness_check')
+        systemProperty key, value
+    }
+
     if (project.hasProperty('slow_tests')) {
         systemProperty 'slow_tests', project.property('slow_tests')
     }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 8cf01de..7508aa1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -50,10 +50,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -246,19 +243,8 @@
         appView.setAppServices(AppServices.builder(appView).build());
       }
       timing.end();
-      new IRConverter(appView, timing, printer).convert(appView, executor);
+      new PrimaryD8L8IRConverter(appView, timing).convert(appView, executor);
       timing.begin("Post conversion");
-      if (options.printCfg) {
-        if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
-          System.out.print(printer.toString());
-        } else {
-          try (OutputStreamWriter writer =
-              new OutputStreamWriter(
-                  new FileOutputStream(options.printCfgFile), StandardCharsets.UTF_8)) {
-            writer.write(printer.toString());
-          }
-        }
-      }
 
       // Close any internal archive providers now the application is fully processed.
       inputApp.closeInternalArchiveProviders();
@@ -450,26 +436,6 @@
     return finalDexApp.build();
   }
 
-  static void optimize(
-      AppView<AppInfo> appView, InternalOptions options, Timing timing, ExecutorService executor)
-      throws IOException, ExecutionException {
-    final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
-
-    new IRConverter(appView, timing, printer).convert(appView, executor);
-
-    if (options.printCfg) {
-      if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
-        System.out.print(printer.toString());
-      } else {
-        try (OutputStreamWriter writer =
-            new OutputStreamWriter(
-                new FileOutputStream(options.printCfgFile), StandardCharsets.UTF_8)) {
-          writer.write(printer.toString());
-        }
-      }
-    }
-  }
-
   static class ConvertedCfFiles implements DexIndexedConsumer, ProgramResourceProvider {
 
     private final List<ProgramResource> resources = new ArrayList<>();
@@ -484,7 +450,7 @@
     }
 
     @Override
-    public Collection<ProgramResource> getProgramResources() throws ResourceException {
+    public Collection<ProgramResource> getProgramResources() {
       return resources;
     }
 
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 69714e7..7950354 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
 import com.android.tools.r8.jar.CfApplicationWriter;
@@ -140,7 +140,7 @@
         AnnotationRemover.clearAnnotations(appView);
       }
 
-      new IRConverter(appView, timing).convert(appView, executor);
+      new PrimaryD8L8IRConverter(appView, timing).convert(appView, executor);
 
       SyntheticFinalization.finalize(appView, timing, executor);
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d9e374d..97467f1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.profile.art.ArtProfileCompletenessChecker.CompletenessExceptions.ALLOW_MISSING_ENUM_UNBOXING_UTILITY_METHODS;
 import static com.android.tools.r8.utils.AssertionUtils.forTesting;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
@@ -18,7 +19,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.AppliedGraphLens;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -39,6 +39,7 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
@@ -71,6 +72,8 @@
 import com.android.tools.r8.optimize.interfaces.analysis.CfOpenClosedInterfacesAnalysis;
 import com.android.tools.r8.optimize.proto.ProtoNormalizer;
 import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.repackaging.Repackaging;
 import com.android.tools.r8.repackaging.RepackagingLens;
 import com.android.tools.r8.shaking.AbstractMethodRemover;
@@ -100,7 +103,6 @@
 import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SelfRetraceTest;
@@ -111,11 +113,8 @@
 import com.google.common.collect.Iterables;
 import com.google.common.io.ByteStreams;
 import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStreamWriter;
 import java.io.PrintStream;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -278,6 +277,8 @@
         SyntheticItems.collectSyntheticInputs(appView);
       }
 
+      assert ArtProfileCompletenessChecker.verify(appView);
+
       // Check for potentially having pass-through of Cf-code for kotlin libraries.
       options.enableCfByteCodePassThrough =
           options.isGeneratingClassFiles() && KotlinMetadataUtils.mayProcessKotlinMetadata(appView);
@@ -302,10 +303,13 @@
           appView.dexItemFactory());
 
       // Upfront desugaring generation: Generates new program classes to be added in the app.
+      ArtProfileCollectionAdditions artProfileCollectionAdditions =
+          ArtProfileCollectionAdditions.create(appView);
       CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer =
-          new CfClassSynthesizerDesugaringEventConsumer();
+          CfClassSynthesizerDesugaringEventConsumer.create(artProfileCollectionAdditions);
       CfClassSynthesizerDesugaringCollection.create(appView)
           .synthesizeClasses(executorService, classSynthesizerEventConsumer);
+      artProfileCollectionAdditions.commit(appView);
       if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
         appView.setAppInfo(
             appView
@@ -370,6 +374,7 @@
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness);
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness);
         assert appView.rootSet().verifyKeptItemsAreKept(appView);
+        assert ArtProfileCompletenessChecker.verify(appView);
         appView.rootSet().checkAllRulesAreUsed(options);
 
         if (options.apiModelingOptions().reportUnknownApiReferences) {
@@ -478,6 +483,8 @@
           classMergingEnqueuerExtensionBuilder.build(appView.graphLens());
       classMergingEnqueuerExtensionBuilder = null;
 
+      assert ArtProfileCompletenessChecker.verify(appView);
+
       if (!isKotlinLibraryCompilationWithInlinePassThrough
           && options.getProguardConfiguration().isOptimizing()) {
         if (options.enableVerticalClassMerging) {
@@ -498,6 +505,8 @@
         }
         assert appView.verticallyMergedClasses() != null;
 
+        assert ArtProfileCompletenessChecker.verify(appView);
+
         HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
             .runIfNecessary(executorService, timing, runtimeTypeCheckInfo);
       }
@@ -520,16 +529,13 @@
       // TODO: we should avoid removing liveness.
       Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
 
-      timing.begin("Create IR");
-      CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
-      try {
-        IRConverter converter = new IRConverter(appView, timing, printer);
-        DexApplication application =
-            converter.optimize(appViewWithLiveness, executorService).asDirect();
-        appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(previous -> application));
-      } finally {
-        timing.end();
-      }
+      assert ArtProfileCompletenessChecker.verify(appView);
+
+      new PrimaryR8IRConverter(appViewWithLiveness, timing)
+          .optimize(appViewWithLiveness, executorService);
+
+      assert ArtProfileCompletenessChecker.verify(
+          appView, ALLOW_MISSING_ENUM_UNBOXING_UTILITY_METHODS);
 
       // Clear the reference type lattice element cache to reduce memory pressure.
       appView.dexItemFactory().clearTypeElementsCache();
@@ -541,18 +547,6 @@
       appView.setGraphLens(new AppliedGraphLens(appView));
       timing.end();
 
-      if (options.printCfg) {
-        if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
-          System.out.print(printer.toString());
-        } else {
-          try (OutputStreamWriter writer = new OutputStreamWriter(
-              new FileOutputStream(options.printCfgFile),
-              StandardCharsets.UTF_8)) {
-            writer.write(printer.toString());
-          }
-        }
-      }
-
       if (options.shouldRerunEnqueuer()) {
         timing.begin("Post optimization code stripping");
         try {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index c874ba9..b4cca13 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -45,9 +45,12 @@
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SemanticVersion;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import java.io.InputStream;
 import java.nio.file.Path;
@@ -64,6 +67,7 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Supplier;
 
 /**
  * Immutable command structure for an invocation of the {@link R8} compiler.
@@ -127,6 +131,7 @@
     private String synthesizedClassPrefix = "";
     private boolean enableMissingLibraryApiModeling = false;
     private boolean enableExperimentalKeepAnnotations = false;
+    private SemanticVersion fakeCompilerVersion = null;
 
     private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder =
         ProguardConfigurationParserOptions.builder().readEnvironment();
@@ -174,6 +179,11 @@
       return self();
     }
 
+    Builder setFakeCompilerVersion(SemanticVersion version) {
+      fakeCompilerVersion = version;
+      return self();
+    }
+
     /**
      * Disable tree shaking.
      *
@@ -588,7 +598,10 @@
       if (proguardConfigurationConsumerForTesting != null) {
         proguardConfigurationConsumerForTesting.accept(configurationBuilder);
       }
+
+      // Add embedded keep rules.
       amendWithRulesAndProvidersForInjarsAndMetaInf(reporter, parser);
+
       // Extract out rules for keep annotations and amend the configuration.
       // TODO(b/248408342): Remove this and parse annotations as part of R8 root-set & enqueuer.
       extractKeepAnnotationRules(parser);
@@ -645,7 +658,8 @@
               getAndroidPlatformBuild(),
               getArtProfilesForRewriting(),
               getStartupProfileProviders(),
-              getClassConflictResolver());
+              getClassConflictResolver(),
+              fakeCompilerVersion);
 
       if (inputDependencyGraphConsumer != null) {
         inputDependencyGraphConsumer.finished();
@@ -656,32 +670,6 @@
     private void amendWithRulesAndProvidersForInjarsAndMetaInf(
         Reporter reporter, ProguardConfigurationParser parser) {
 
-      // Process Proguard configurations supplied through data resources in the input.
-      DataResourceProvider.Visitor embeddedProguardConfigurationVisitor =
-          new DataResourceProvider.Visitor() {
-            @Override
-            public void visit(DataDirectoryResource directory) {
-              // Don't do anything.
-            }
-
-            @Override
-            public void visit(DataEntryResource resource) {
-              if (resource.getName().startsWith("META-INF/proguard/")) {
-                try (InputStream in = resource.getByteStream()) {
-                  ProguardConfigurationSource source =
-                      new ProguardConfigurationSourceBytes(in, resource.getOrigin());
-                  parser.parse(source);
-                } catch (ResourceException e) {
-                  reporter.error(
-                      new StringDiagnostic(
-                          "Failed to open input: " + e.getMessage(), resource.getOrigin()));
-                } catch (Exception e) {
-                  reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
-                }
-              }
-            }
-          };
-
       // Since -injars can itself reference archives with rules and that in turn have -injars the
       // completion of amending rules and providers must run in a fixed point. The fixed point is
       // reached once the injars set is stable.
@@ -700,11 +688,39 @@
         if (providers.isEmpty()) {
           return;
         }
+
+        Supplier<SemanticVersion> semanticVersionSupplier =
+            Suppliers.memoize(
+                () -> {
+                  SemanticVersion compilerVersion =
+                      fakeCompilerVersion == null
+                          ? SemanticVersion.create(
+                              Version.getMajorVersion(),
+                              Version.getMinorVersion(),
+                              Version.getPatchVersion())
+                          : fakeCompilerVersion;
+                  if (compilerVersion.getMajor() < 0) {
+                    compilerVersion = SemanticVersion.parse(Version.ACTIVE_DEV_VERSION);
+                    reporter.warning(
+                        "Running R8 version "
+                            + Version.getVersionString()
+                            + " which cannot be represented as a semantic version. Using"
+                            + " version "
+                            + compilerVersion
+                            + " for selecting Proguard configurations embedded under"
+                            + " META-INF/");
+                  }
+                  return compilerVersion;
+                });
+
         while (!providers.isEmpty()) {
           DataResourceProvider dataResourceProvider = providers.pop().getDataResourceProvider();
           if (dataResourceProvider != null) {
             try {
+              ExtractEmbeddedRules embeddedProguardConfigurationVisitor =
+                  new ExtractEmbeddedRules(reporter, semanticVersionSupplier);
               dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
+              embeddedProguardConfigurationVisitor.parseRelevantRules(parser);
             } catch (ResourceException e) {
               reporter.error(new ExceptionDiagnostic(e));
             }
@@ -828,6 +844,7 @@
   private final FeatureSplitConfiguration featureSplitConfiguration;
   private final String synthesizedClassPrefix;
   private final boolean enableMissingLibraryApiModeling;
+  private final SemanticVersion fakeCompilerVersion;
 
   /** Get a new {@link R8Command.Builder}. */
   public static Builder builder() {
@@ -920,7 +937,8 @@
       boolean isAndroidPlatformBuild,
       List<ArtProfileForRewriting> artProfilesForRewriting,
       List<StartupProfileProvider> startupProfileProviders,
-      ClassConflictResolver classConflictResolver) {
+      ClassConflictResolver classConflictResolver,
+      SemanticVersion fakeCompilerVersion) {
     super(
         inputApp,
         mode,
@@ -963,6 +981,7 @@
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.synthesizedClassPrefix = synthesizedClassPrefix;
     this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
+    this.fakeCompilerVersion = fakeCompilerVersion;
   }
 
   private R8Command(boolean printHelp, boolean printVersion) {
@@ -986,6 +1005,7 @@
     featureSplitConfiguration = null;
     synthesizedClassPrefix = null;
     enableMissingLibraryApiModeling = false;
+    fakeCompilerVersion = null;
   }
 
   public DexItemFactory getDexItemFactory() {
@@ -1204,4 +1224,126 @@
         .setEnableMissingLibraryApiModeling(enableMissingLibraryApiModeling)
         .build();
   }
+
+  private static class ExtractEmbeddedRules implements DataResourceProvider.Visitor {
+
+    private final Supplier<SemanticVersion> compilerVersionSupplier;
+    private final Reporter reporter;
+    private final List<ProguardConfigurationSource> proguardSources = new ArrayList<>();
+    private final List<ProguardConfigurationSource> r8Sources = new ArrayList<>();
+    private SemanticVersion compilerVersion;
+
+    public ExtractEmbeddedRules(
+        Reporter reporter, Supplier<SemanticVersion> compilerVersionSupplier) {
+      this.compilerVersionSupplier = compilerVersionSupplier;
+      this.reporter = reporter;
+    }
+
+    @Override
+    public void visit(DataDirectoryResource directory) {
+      // Don't do anything.
+    }
+
+    @Override
+    public void visit(DataEntryResource resource) {
+      if (relevantProguardResource(resource)) {
+        assert !relevantR8Resource(resource);
+        readProguardConfigurationSource(resource, proguardSources::add);
+      } else if (relevantR8Resource(resource)) {
+        assert !relevantProguardResource(resource);
+        readProguardConfigurationSource(resource, r8Sources::add);
+      }
+    }
+
+    private void readProguardConfigurationSource(
+        DataEntryResource resource, Consumer<ProguardConfigurationSource> consumer) {
+      try (InputStream in = resource.getByteStream()) {
+        consumer.accept(new ProguardConfigurationSourceBytes(in, resource.getOrigin()));
+      } catch (ResourceException e) {
+        reporter.error(
+            new StringDiagnostic("Failed to open input: " + e.getMessage(), resource.getOrigin()));
+      } catch (Exception e) {
+        reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
+      }
+    }
+
+    private boolean relevantProguardResource(DataEntryResource resource) {
+      // Configurations in META-INF/com.android.tools/proguard/ are ignored.
+      final String proguardPrefix = "META-INF/proguard";
+      if (!resource.getName().startsWith(proguardPrefix)) {
+        return false;
+      }
+      String withoutPrefix = resource.getName().substring(proguardPrefix.length());
+      return withoutPrefix.startsWith("/");
+    }
+
+    private boolean relevantR8Resource(DataEntryResource resource) {
+      final String r8Prefix = "META-INF/com.android.tools/r8";
+      if (!resource.getName().startsWith(r8Prefix)) {
+        return false;
+      }
+      String withoutPrefix = resource.getName().substring(r8Prefix.length());
+      if (withoutPrefix.startsWith("/")) {
+        // Everything under META-INF/com.android.tools/r8/ is included (not version specific).
+        return true;
+      }
+      // Expect one of the following patterns:
+      //   com.android.tools/r8-min-1.5.0/
+      //   com.android.tools/r8-max-1.5.99/
+      //   com.android.tools/r8-min-1.5.0-max-1.5.99/
+      final String minPrefix = "-min-";
+      final String maxPrefix = "-max-";
+      if (!withoutPrefix.startsWith(minPrefix) && !withoutPrefix.startsWith(maxPrefix)) {
+        return false;
+      }
+
+      SemanticVersion from = SemanticVersion.min();
+      SemanticVersion to = SemanticVersion.max();
+
+      if (withoutPrefix.startsWith(minPrefix)) {
+        withoutPrefix = withoutPrefix.substring(minPrefix.length());
+        int versionEnd = StringUtils.indexOf(withoutPrefix, '-', '/');
+        if (versionEnd == -1) {
+          return false;
+        }
+        try {
+          from = SemanticVersion.parse(withoutPrefix.substring(0, versionEnd));
+        } catch (IllegalArgumentException e) {
+          return false;
+        }
+        withoutPrefix = withoutPrefix.substring(versionEnd);
+      }
+      if (withoutPrefix.startsWith(maxPrefix)) {
+        withoutPrefix = withoutPrefix.substring(maxPrefix.length());
+        int versionEnd = withoutPrefix.indexOf('/');
+        if (versionEnd == -1) {
+          return false;
+        }
+        try {
+          to = SemanticVersion.parse(withoutPrefix.substring(0, versionEnd));
+        } catch (IllegalArgumentException e) {
+          return false;
+        }
+      }
+      if (compilerVersion == null) {
+        compilerVersion = compilerVersionSupplier.get();
+      }
+      return compilerVersion.isNewerOrEqual(from) && to.isNewerOrEqual(compilerVersion);
+    }
+
+    private void parse(
+        List<ProguardConfigurationSource> sources, ProguardConfigurationParser parser) {
+      for (ProguardConfigurationSource source : sources) {
+        try {
+          parser.parse(source);
+        } catch (Exception e) {
+          reporter.error(new ExceptionDiagnostic(e, source.getOrigin()));
+        }
+      }
+    }
+
+    void parseRelevantRules(ProguardConfigurationParser parser) {
+      parse(!r8Sources.isEmpty() ? r8Sources : proguardSources, parser);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 5dd6284..6eac09c 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -9,10 +9,17 @@
 /** Version of the D8/R8 library. */
 public final class Version {
 
+  private static final String MAIN_LABEL = "main";
+
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
   public static final String LABEL = "8.1.18-dev";
 
+  // The prefix of the active dev version line being worked on from this branch. This is used in the
+  // few cases where the compiler makes decisions based in the compiler version and where version
+  // 'main' cannot be used.
+  public static final String ACTIVE_DEV_VERSION = "8.1.0";
+
   private Version() {
   }
 
@@ -31,7 +38,7 @@
   }
 
   static int getMajorVersion(String label) {
-    if (label.equals("main")) {
+    if (label.equals(MAIN_LABEL)) {
       return -1;
     }
     int start = 0;
@@ -49,7 +56,7 @@
   }
 
   static int getMinorVersion(String label) {
-    if (label.equals("main")) {
+    if (label.equals(MAIN_LABEL)) {
       return -1;
     }
     int start = label.indexOf('.') + 1;
@@ -67,7 +74,7 @@
   }
 
   static int getPatchVersion(String label) {
-    if (label.equals("main")) {
+    if (label.equals(MAIN_LABEL)) {
       return -1;
     }
     int skip = label.indexOf('.') + 1;
@@ -87,7 +94,7 @@
   }
 
   static String getPreReleaseString(String label) {
-    if (label.equals("main")) {
+    if (label.equals(MAIN_LABEL)) {
       return null;
     }
     int start = label.indexOf('-') + 1;
@@ -107,6 +114,10 @@
   }
 
   static boolean isDevelopmentVersion(String label, boolean isEngineering) {
-    return label.equals("main") || label.endsWith("-dev") || isEngineering;
+    return label.equals(MAIN_LABEL) || label.endsWith("-dev") || isEngineering;
+  }
+
+  public static boolean isMainVersion() {
+    return LABEL.equals(MAIN_LABEL);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
index 35eb379..fb97359 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -19,7 +19,7 @@
   private final KnownApiLevel[] knownApiLevelCache;
 
   public AndroidApiLevelCompute() {
-    knownApiLevelCache = new KnownApiLevel[AndroidApiLevel.LATEST.getLevel() + 1];
+    knownApiLevelCache = new KnownApiLevel[AndroidApiLevel.API_DATABASE_LEVEL.getLevel() + 1];
     for (AndroidApiLevel value : AndroidApiLevel.values()) {
       if (value != AndroidApiLevel.ANDROID_PLATFORM && value != AndroidApiLevel.MASTER) {
         knownApiLevelCache[value.getLevel()] = new KnownApiLevel(value);
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 586e8e5..c1af115 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ThrowExceptionCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.CommittedItems;
@@ -42,50 +41,52 @@
 public class ApiReferenceStubber {
 
   private final AppView<?> appView;
-  private final Map<DexLibraryClass, Set<ProgramDefinition>> referencingContexts =
+  private final Map<DexLibraryClass, Set<DexProgramClass>> referencingContexts =
       new ConcurrentHashMap<>();
   private final Set<DexLibraryClass> libraryClassesToMock = Sets.newConcurrentHashSet();
   private final Set<DexType> seenTypes = Sets.newConcurrentHashSet();
   private final AndroidApiLevelCompute apiLevelCompute;
+  private final ApiReferenceStubberEventConsumer eventConsumer;
 
   public ApiReferenceStubber(AppView<?> appView) {
     this.appView = appView;
-    apiLevelCompute = appView.apiLevelCompute();
+    this.apiLevelCompute = appView.apiLevelCompute();
+    this.eventConsumer = ApiReferenceStubberEventConsumer.create(appView);
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
-    if (appView.options().isGeneratingClassFiles()
-        || !appView.options().apiModelingOptions().enableStubbingOfClasses) {
-      return;
+    if (appView.options().isGeneratingDex()
+        && appView.options().apiModelingOptions().enableStubbingOfClasses) {
+      ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService);
     }
-    ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService);
-    if (libraryClassesToMock.isEmpty()) {
-      return;
+    if (!libraryClassesToMock.isEmpty()) {
+      libraryClassesToMock.forEach(
+          clazz ->
+              mockMissingLibraryClass(
+                  clazz,
+                  ThrowExceptionCode.create(appView.dexItemFactory().noClassDefFoundErrorType),
+                  eventConsumer));
+      // Commit the synthetic items.
+      CommittedItems committedItems = appView.getSyntheticItems().commit(appView.appInfo().app());
+      if (appView.hasLiveness()) {
+        AppView<AppInfoWithLiveness> appInfoWithLivenessAppView = appView.withLiveness();
+        appInfoWithLivenessAppView.setAppInfo(
+            appInfoWithLivenessAppView.appInfo().rebuildWithLiveness(committedItems));
+      } else if (appView.hasClassHierarchy()) {
+        appView
+            .withClassHierarchy()
+            .setAppInfo(
+                appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(committedItems));
+      } else {
+        appView
+            .withoutClassHierarchy()
+            .setAppInfo(
+                new AppInfo(
+                    appView.appInfo().getSyntheticItems().commit(appView.app()),
+                    appView.appInfo().getMainDexInfo()));
+      }
     }
-    libraryClassesToMock.forEach(
-        clazz ->
-            mockMissingLibraryClass(
-                clazz,
-                ThrowExceptionCode.create(appView.dexItemFactory().noClassDefFoundErrorType)));
-    // Commit the synthetic items.
-    CommittedItems committedItems = appView.getSyntheticItems().commit(appView.appInfo().app());
-    if (appView.hasLiveness()) {
-      AppView<AppInfoWithLiveness> appInfoWithLivenessAppView = appView.withLiveness();
-      appInfoWithLivenessAppView.setAppInfo(
-          appInfoWithLivenessAppView.appInfo().rebuildWithLiveness(committedItems));
-    } else if (appView.hasClassHierarchy()) {
-      appView
-          .withClassHierarchy()
-          .setAppInfo(
-              appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(committedItems));
-    } else {
-      appView
-          .withoutClassHierarchy()
-          .setAppInfo(
-              new AppInfo(
-                  appView.appInfo().getSyntheticItems().commit(appView.app()),
-                  appView.appInfo().getMainDexInfo()));
-    }
+    eventConsumer.finished(appView);
   }
 
   public void processClass(DexProgramClass clazz) {
@@ -142,7 +143,8 @@
 
   private void mockMissingLibraryClass(
       DexLibraryClass libraryClass,
-      ThrowExceptionCode throwExceptionCode) {
+      ThrowExceptionCode throwExceptionCode,
+      ApiReferenceStubberEventConsumer eventConsumer) {
     DexItemFactory factory = appView.dexItemFactory();
     // Do not stub the anything starting with java (including the object type).
     if (libraryClass.getType() == appView.dexItemFactory().objectType
@@ -159,38 +161,44 @@
         .isSupported(libraryClass.getType())) {
       return;
     }
-    Set<ProgramDefinition> contexts = referencingContexts.get(libraryClass);
+    Set<DexProgramClass> contexts = referencingContexts.get(libraryClass);
     if (contexts == null) {
       throw new Unreachable("Attempt to create a global synthetic with no contexts");
     }
-    appView
-        .appInfo()
-        .getSyntheticItems()
-        .ensureGlobalClass(
-            () -> new MissingGlobalSyntheticsConsumerDiagnostic("API stubbing"),
-            kinds -> kinds.API_MODEL_STUB,
-            libraryClass.getType(),
-            contexts,
-            appView,
-            classBuilder -> {
-              classBuilder
-                  .setSuperType(libraryClass.getSuperType())
-                  .setInterfaces(Arrays.asList(libraryClass.getInterfaces().values))
-                  // Add throwing static initializer
-                  .addMethod(
-                      methodBuilder ->
-                          methodBuilder
-                              .setName(factory.classConstructorMethodName)
-                              .setProto(factory.createProto(factory.voidType))
-                              .setAccessFlags(MethodAccessFlags.createForClassInitializer())
-                              .setCode(method -> throwExceptionCode));
-              if (libraryClass.isInterface()) {
-                classBuilder.setInterface();
-              }
-              if (!libraryClass.isFinal()) {
-                classBuilder.unsetFinal();
-              }
-            },
-            ignored -> {});
+    DexProgramClass mockClass =
+        appView
+            .appInfo()
+            .getSyntheticItems()
+            .ensureGlobalClass(
+                () -> new MissingGlobalSyntheticsConsumerDiagnostic("API stubbing"),
+                kinds -> kinds.API_MODEL_STUB,
+                libraryClass.getType(),
+                contexts,
+                appView,
+                classBuilder -> {
+                  classBuilder
+                      .setSuperType(libraryClass.getSuperType())
+                      .setInterfaces(Arrays.asList(libraryClass.getInterfaces().values))
+                      // Add throwing static initializer
+                      .addMethod(
+                          methodBuilder ->
+                              methodBuilder
+                                  .setName(factory.classConstructorMethodName)
+                                  .setProto(factory.createProto(factory.voidType))
+                                  .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+                                  .setCode(method -> throwExceptionCode));
+                  if (libraryClass.isInterface()) {
+                    classBuilder.setInterface();
+                  }
+                  if (!libraryClass.isFinal()) {
+                    classBuilder.unsetFinal();
+                  }
+                },
+                clazz -> eventConsumer.acceptMockedLibraryClass(clazz, libraryClass));
+    if (!eventConsumer.isEmpty()) {
+      for (DexProgramClass context : contexts) {
+        eventConsumer.acceptMockedLibraryClassContext(mockClass, libraryClass, context);
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubberEventConsumer.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubberEventConsumer.java
new file mode 100644
index 0000000..ac79346
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubberEventConsumer.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2023, 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.androidapi;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingApiReferenceStubberEventConsumer;
+import com.android.tools.r8.profile.art.rewriting.ConcreteArtProfileCollectionAdditions;
+
+public interface ApiReferenceStubberEventConsumer {
+
+  void acceptMockedLibraryClass(DexProgramClass mockClass, DexLibraryClass libraryClass);
+
+  void acceptMockedLibraryClassContext(
+      DexProgramClass mockClass, DexLibraryClass libraryClass, DexProgramClass context);
+
+  default void finished(AppView<?> appView) {}
+
+  boolean isEmpty();
+
+  static ApiReferenceStubberEventConsumer create(AppView<?> appView) {
+    if (appView.options().getArtProfileOptions().isIncludingApiReferenceStubs()) {
+      ArtProfileCollectionAdditions artProfileCollectionAdditions =
+          ArtProfileCollectionAdditions.create(appView);
+      if (!artProfileCollectionAdditions.isNop()) {
+        return create(artProfileCollectionAdditions.asConcrete());
+      }
+    }
+    return empty();
+  }
+
+  static ApiReferenceStubberEventConsumer create(
+      ConcreteArtProfileCollectionAdditions artProfileCollectionAdditions) {
+    return ArtProfileRewritingApiReferenceStubberEventConsumer.attach(
+        artProfileCollectionAdditions, empty());
+  }
+
+  static EmptyApiReferenceStubberEventConsumer empty() {
+    return EmptyApiReferenceStubberEventConsumer.getInstance();
+  }
+
+  class EmptyApiReferenceStubberEventConsumer implements ApiReferenceStubberEventConsumer {
+
+    private static final EmptyApiReferenceStubberEventConsumer INSTANCE =
+        new EmptyApiReferenceStubberEventConsumer();
+
+    private EmptyApiReferenceStubberEventConsumer() {}
+
+    static EmptyApiReferenceStubberEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptMockedLibraryClass(DexProgramClass mockClass, DexLibraryClass libraryClass) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptMockedLibraryClassContext(
+        DexProgramClass mockClass, DexLibraryClass libraryClass, DexProgramClass context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return true;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
index d9cabaa..a1a7eb2 100644
--- a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -92,6 +93,7 @@
   }
 
   public void putInstructions(
+      AppView<?> appView,
       DexWritableCode code,
       ProgramMethod context,
       ObjectToOffsetMapping mapping,
@@ -101,7 +103,12 @@
     assert byteBuffer.position() % 2 == 0;
     ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
     code.writeDex(
-        shortBuffer, context, mapping.getGraphLens(), mapping.getLensCodeRewriter(), mapping);
+        shortBuffer,
+        context,
+        mapping.getGraphLens(),
+        code.getCodeLens(appView),
+        mapping.getLensCodeRewriter(),
+        mapping);
     code.writeKeepRulesForDesugaredLibrary(desugaredLibraryCodeToKeep);
     byteBuffer.position(byteBuffer.position() + shortBuffer.position() * Short.BYTES);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index fc9c037..6d43e68 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -547,7 +547,7 @@
     int insnSizeOffset = dest.position();
     dest.forward(4);
     // Write instruction stream.
-    dest.putInstructions(code, method, mapping, desugaredLibraryCodeToKeep);
+    dest.putInstructions(appView, code, method, mapping, desugaredLibraryCodeToKeep);
     // Compute size and do the backward/forward dance to write the size at the beginning.
     int insnSize = dest.position() - insnSizeOffset - 4;
     dest.rewind(insnSize + 4);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java b/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java
index 650655a..6d95477 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexCheckCast.java
@@ -74,6 +74,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexType rewritten = graphLens.lookupType(getType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java b/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java
index bc476c1..791eef5 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstClass.java
@@ -74,6 +74,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexType rewritten = graphLens.lookupType(getType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
index b909f64..7cf55ad 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodHandle.java
@@ -78,6 +78,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexMethodHandle rewritten =
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
index 15bee8c..a4e1941 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstMethodType.java
@@ -76,6 +76,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexProto rewritten = rewriter.rewriteProto(getMethodType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexConstString.java b/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
index e143d89..da79e38 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexConstString.java
@@ -89,6 +89,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     int index = BBBB.getOffset(mapping);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java b/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java
index 228258d..62b99a3 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFillArrayDataPayload.java
@@ -56,6 +56,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(3, dest); // Pseudo-opcode = 0x0300
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java
index ab52b2b..08d174c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArray.java
@@ -72,6 +72,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexType rewritten = graphLens.lookupType(getType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java
index 5e473dc..5a7ab7d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFilledNewArrayRange.java
@@ -72,6 +72,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexType rewritten = graphLens.lookupType(getType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
index ba29cbe..1b1ad0c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat10t.java
@@ -35,6 +35,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
index 174c89b..ce80312 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat10x.java
@@ -27,6 +27,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
index 09e3fb6..805512d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat11n.java
@@ -49,6 +49,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
index 393ab88..7a5ada4 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat11x.java
@@ -35,6 +35,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
index 979bcc7..711072d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat12x.java
@@ -43,6 +43,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
index a03b9c5..e226b0b 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat20t.java
@@ -34,6 +34,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java
index 246c6cc..40d5684 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21h.java
@@ -43,6 +43,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
index 3d66ad2..2351a2c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21s.java
@@ -45,6 +45,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
index 93cd025..c2fad2b 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat21t.java
@@ -47,6 +47,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
index 222fc09..f9c8419 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22b.java
@@ -49,6 +49,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
index 53d247c..715f719 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22s.java
@@ -49,6 +49,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
index c276ca1..4ca9fcf 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22t.java
@@ -51,6 +51,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(B, A, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
index 3cd881d..9d8f38c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat22x.java
@@ -44,6 +44,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
index 1e1b130..c6ee23e 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat23x.java
@@ -48,6 +48,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
index cfd2bf7..df14fb9 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat30t.java
@@ -33,6 +33,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
index ae0ca6c..d5b1037 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31c.java
@@ -47,6 +47,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
index 942c5e2..67ae1b5 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31i.java
@@ -43,6 +43,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
index 7f26c21..07cea77 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat31t.java
@@ -43,6 +43,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
index 5389c2f..7cd9e2c 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat32x.java
@@ -45,6 +45,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(0, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
index 2f4adc5..91657d8 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat45cc.java
@@ -119,6 +119,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     // The method is one of java.lang.MethodHandle.invoke/invokeExact.
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
index 8aa065a..0bf1e92 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat4rcc.java
@@ -57,6 +57,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     // The method is one of java.lang.MethodHandle.invoke/invokeExact.
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java b/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
index ddce5ad..15cb63a 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexFormat51l.java
@@ -43,6 +43,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(AA, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java b/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java
index d1c298a..1f77b9f 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexIgetOrIput.java
@@ -42,9 +42,10 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexField lookup = graphLens.lookupField(getField());
+    DexField lookup = graphLens.lookupField(getField(), codeLens);
     writeFirst(B, A, dest);
     write16BitReference(lookup, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java b/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
index 8591fd9..77a2f9f 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInitClass.java
@@ -118,6 +118,7 @@
       ShortBuffer buffer,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     // We intentionally apply the graph lens first, and then the init class lens, using the fact
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java b/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java
index 601dffb..ba204cd 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstanceOf.java
@@ -88,6 +88,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexType lookup = graphLens.lookupType(getType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
index e6362d0..118556a 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInstruction.java
@@ -393,6 +393,7 @@
       ShortBuffer buffer,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter);
 
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java
index 605b420..28464af 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustom.java
@@ -79,6 +79,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(A, G, dest);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java
index a88f338..51f0908 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeCustomRange.java
@@ -79,6 +79,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexCallSite rewritten = rewriter.rewriteCallSite(getCallSite(), context);
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java
index 9340e4a..a390eff 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethod.java
@@ -50,6 +50,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     MethodLookupResult lookup =
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java
index 34243ac..3c1ffc7 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexInvokeMethodRange.java
@@ -50,6 +50,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     MethodLookupResult lookup =
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
index ddaf074..451eef1 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexItemBasedConstString.java
@@ -101,6 +101,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     throw new Unreachable(
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java b/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java
index 8d35555..285b82e 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexNewArray.java
@@ -78,6 +78,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexType lookup = graphLens.lookupType(getType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java b/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java
index 4382e71..e3bc04d 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexNewInstance.java
@@ -65,6 +65,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     DexType rewritten = graphLens.lookupType(getType());
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java
index 387952c..3992859 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexNewUnboxedEnumInstance.java
@@ -64,6 +64,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     throw new Unreachable();
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java b/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java
index 95f6eed..77b35f6 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexPackedSwitchPayload.java
@@ -52,6 +52,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(1, dest); // Pseudo-opcode = 0x0100
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java b/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
index 3101663..8f0b9e8 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexRecordFieldValues.java
@@ -117,6 +117,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     throw new Unreachable(
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java b/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java
index 85c8ea3..0e9b3b8 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexSgetOrSput.java
@@ -38,9 +38,10 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    DexField rewritten = graphLens.lookupField(getField());
+    DexField rewritten = graphLens.lookupField(getField(), codeLens);
     writeFirst(AA, dest);
     write16BitReference(rewritten, dest, mapping);
   }
diff --git a/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java b/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java
index 838e4ea..f0059d9 100644
--- a/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/dex/code/DexSparseSwitchPayload.java
@@ -56,6 +56,7 @@
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
     writeFirst(2, dest); // Pseudo-opcode = 0x0200
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
index c3a72b9..72908ca 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.IRToDexFinalizer;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.startup.generated.InstrumentationServerFactory;
 import com.android.tools.r8.startup.generated.InstrumentationServerImplFactory;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -97,7 +98,8 @@
     }
 
     List<DexProgramClass> extraProgramClasses = createStartupRuntimeLibraryClasses();
-    converter.processClassesConcurrently(extraProgramClasses, executorService);
+    MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
+    converter.processClassesConcurrently(extraProgramClasses, eventConsumer, executorService);
 
     DexApplication newApplication =
         appView.app().builder().addProgramClasses(extraProgramClasses).build();
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 6a0652a..b5fe77a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -199,7 +199,7 @@
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo) {
     return new AppView<>(
         appInfo,
-        ArtProfileCollection.createInitialArtProfileCollection(appInfo.options()),
+        ArtProfileCollection.createInitialArtProfileCollection(appInfo, appInfo.options()),
         WholeProgramOptimizations.OFF,
         defaultTypeRewriter(appInfo));
   }
@@ -216,7 +216,7 @@
       T appInfo, TypeRewriter mapper, Timing timing) {
     return new AppView<>(
         appInfo,
-        ArtProfileCollection.createInitialArtProfileCollection(appInfo.options()),
+        ArtProfileCollection.createInitialArtProfileCollection(appInfo, appInfo.options()),
         WholeProgramOptimizations.OFF,
         mapper,
         timing);
@@ -240,7 +240,7 @@
             startupOrder);
     return new AppView<>(
         appInfo,
-        ArtProfileCollection.createInitialArtProfileCollection(application.options),
+        ArtProfileCollection.createInitialArtProfileCollection(appInfo, appInfo.options()),
         WholeProgramOptimizations.ON,
         defaultTypeRewriter(appInfo));
   }
@@ -248,7 +248,7 @@
   public static <T extends AppInfo> AppView<T> createForL8(T appInfo, TypeRewriter mapper) {
     return new AppView<>(
         appInfo,
-        ArtProfileCollection.createInitialArtProfileCollection(appInfo.options()),
+        ArtProfileCollection.createInitialArtProfileCollection(appInfo, appInfo.options()),
         WholeProgramOptimizations.OFF,
         mapper);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index c239b75..a3c20e3 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.contexts.CompilationContext;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.kotlin.Kotlin;
@@ -176,8 +177,10 @@
   private void writeIR(ProgramMethod method, PrintStream ps) {
     CfgPrinter printer = new CfgPrinter();
     IRConverter converter = new IRConverter(appInfo, timing, printer);
+    MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
     OneTimeMethodProcessor methodProcessor =
-        OneTimeMethodProcessor.create(method, compilationContext.createProcessorContext());
+        OneTimeMethodProcessor.create(
+            method, eventConsumer, compilationContext.createProcessorContext());
     methodProcessor.forEachWaveWithExtension(
         (ignore, methodProcessingContext) ->
             converter.processDesugaredMethod(
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 165ff81..601ce08 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -371,13 +371,14 @@
       ShortBuffer shortBuffer,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping) {
     DexMethod parentConstructor = getParentConstructor(context, mapping.dexItemFactory());
     MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(parentConstructor, context);
     new DexInvokeDirect(1, lookupResult.getReference(), 0, 0, 0, 0, 0)
-        .write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
-    new DexReturnVoid().write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+        .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
+    new DexReturnVoid().write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index c7e251e..8c7c2ff 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -90,6 +90,20 @@
         .withItemArray(c -> c.instructions);
   }
 
+  private DexCode(DexCode code) {
+    this(
+        code.registerSize,
+        code.incomingRegisterSize,
+        code.outgoingRegisterSize,
+        code.instructions,
+        code.tries,
+        code.handlers,
+        code.debugInfo,
+        code.metadata);
+    this.debugInfoForWriting = code.debugInfoForWriting;
+    this.highestSortingString = code.highestSortingString;
+  }
+
   public DexCode(int registerSize, int insSize, int outsSize, DexInstruction[] instructions) {
     this(
         registerSize,
@@ -144,6 +158,16 @@
     hashCode();  // Cache the hash code eagerly.
   }
 
+  public DexCode withCodeLens(GraphLens codeLens) {
+    return new DexCode(this) {
+
+      @Override
+      public GraphLens getCodeLens(AppView<?> appView) {
+        return codeLens;
+      }
+    };
+  }
+
   @Override
   public DexCode self() {
     return this;
@@ -803,10 +827,11 @@
       ShortBuffer shortBuffer,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping) {
     for (DexInstruction instruction : instructions) {
-      instruction.write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+      instruction.write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
     }
   }
 
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 1ba1122..4ffba91 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -6,6 +6,7 @@
 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;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
@@ -35,17 +36,17 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.base.Strings;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -53,20 +54,20 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class DexItemFactory {
@@ -2640,12 +2641,21 @@
   }
 
   public DexMethod createInstanceInitializerWithFreshProto(
-      DexMethod method, List<DexType> extraTypes, Predicate<DexMethod> isFresh) {
+      DexMethod method, List<Supplier<DexType>> extraTypes, Predicate<DexMethod> isFresh) {
+    return createInstanceInitializerWithFreshProto(method, extraTypes, isFresh, emptyConsumer());
+  }
+
+  public DexMethod createInstanceInitializerWithFreshProto(
+      DexMethod method,
+      List<Supplier<DexType>> extraTypes,
+      Predicate<DexMethod> isFresh,
+      Consumer<Set<DexType>> usedExtraTypesConsumer) {
     assert method.isInstanceInitializer(this);
     return createInstanceInitializerWithFreshProto(
         method.proto,
         extraTypes,
-        proto -> Optional.of(method.withProto(proto, this)).filter(isFresh));
+        proto -> Optional.of(method.withProto(proto, this)).filter(isFresh),
+        usedExtraTypesConsumer);
   }
 
   public DexMethod createInstanceInitializerWithFreshProto(
@@ -2653,31 +2663,64 @@
     assert method.isInstanceInitializer(this);
     return createInstanceInitializerWithFreshProto(
         method.proto,
-        ImmutableList.of(extraType),
-        proto -> Optional.of(method.withProto(proto, this)).filter(isFresh));
+        ImmutableList.of(() -> extraType),
+        proto -> Optional.of(method.withProto(proto, this)).filter(isFresh),
+        emptyConsumer());
+  }
+
+  private class FreshInstanceInitializerCandidate {
+
+    DexProto protoWithoutExtraType;
+    Supplier<DexType> extraTypeSupplier;
+    Set<DexType> usedExtraTypes;
+
+    FreshInstanceInitializerCandidate(
+        DexProto protoWithoutExtraType,
+        Supplier<DexType> extraTypeSupplier,
+        Set<DexType> usedExtraTypes) {
+      this.protoWithoutExtraType = protoWithoutExtraType;
+      this.extraTypeSupplier = extraTypeSupplier;
+      this.usedExtraTypes = SetUtils.newIdentityHashSet(usedExtraTypes);
+    }
+
+    DexProto createProto() {
+      DexType extraType = extraTypeSupplier.get();
+      usedExtraTypes.add(extraType);
+      return appendTypeToProto(protoWithoutExtraType, extraType);
+    }
   }
 
   private DexMethod createInstanceInitializerWithFreshProto(
-      DexProto proto, List<DexType> extraTypes, Function<DexProto, Optional<DexMethod>> isFresh) {
-    Queue<Iterable<DexProto>> tryProtos = new LinkedList<>();
-    Iterator<DexProto> current = IterableUtils.singleton(proto).iterator();
-
+      DexProto proto,
+      List<Supplier<DexType>> extraTypes,
+      Function<DexProto, Optional<DexMethod>> isFresh,
+      Consumer<Set<DexType>> usedExtraTypesConsumer) {
+    Optional<DexMethod> resultWithNoExtraTypes = isFresh.apply(proto);
+    if (resultWithNoExtraTypes.isPresent()) {
+      return resultWithNoExtraTypes.get();
+    }
+    assert !extraTypes.isEmpty();
+    Deque<FreshInstanceInitializerCandidate> worklist =
+        DequeUtils.newArrayDeque(
+            new FreshInstanceInitializerCandidate(
+                proto, extraTypes.iterator().next(), Collections.emptySet()));
     int count = 0;
     while (true) {
       assert count++ < 100;
-      if (!current.hasNext()) {
-        assert !tryProtos.isEmpty();
-        current = tryProtos.remove().iterator();
-        assert current.hasNext();
-      }
-      DexProto tryProto = current.next();
+      assert !worklist.isEmpty();
+      FreshInstanceInitializerCandidate candidate = worklist.removeFirst();
+      DexProto tryProto = candidate.createProto();
       Optional<DexMethod> object = isFresh.apply(tryProto);
       if (object.isPresent()) {
+        assert !candidate.usedExtraTypes.isEmpty();
+        usedExtraTypesConsumer.accept(candidate.usedExtraTypes);
         return object.get();
       }
-      assert !extraTypes.isEmpty();
-      tryProtos.add(
-          Iterables.transform(extraTypes, extraType -> appendTypeToProto(tryProto, extraType)));
+      for (Supplier<DexType> extraTypeSupplier : extraTypes) {
+        worklist.addLast(
+            new FreshInstanceInitializerCandidate(
+                tryProto, extraTypeSupplier, candidate.usedExtraTypes));
+      }
     }
   }
 
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 0cb6881..22d3978 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -339,6 +339,12 @@
     return toProgramMethodOrNull(getClassInitializer());
   }
 
+  public void acceptProgramClassInitializer(Consumer<ProgramMethod> consumer) {
+    if (hasClassInitializer()) {
+      consumer.accept(getProgramClassInitializer());
+    }
+  }
+
   public ProgramMethod getProgramDefaultInitializer() {
     return getProgramInitializer(DexType.EMPTY_ARRAY);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
index 1458f08..af4b8a0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -63,6 +63,8 @@
 
   void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep);
 
+  GraphLens getCodeLens(AppView<?> appView);
+
   DexDebugInfoForWriting getDebugInfoForWriting();
 
   DexWritableCodeKind getDexWritableCodeKind();
@@ -99,6 +101,7 @@
       ShortBuffer shortBuffer,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
index fe15a02..4c61282 100644
--- a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -135,7 +135,7 @@
     if (this == codeLens) {
       return originalField;
     }
-    DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
+    DexField renamedField = getPrevious().getRenamedFieldSignature(originalField, codeLens);
     return internalGetNextFieldSignature(renamedField);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index dc18cfe..57baddf 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -211,19 +211,21 @@
       ShortBuffer shortBuffer,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping) {
     int register = 0;
     int notUsed = 0;
     int argumentCount = 1;
     new DexNewInstance(register, exceptionType)
-        .write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+        .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
     DexMethod instanceInitializer =
         lensCodeRewriter.dexItemFactory().createInstanceInitializer(exceptionType);
     new DexInvokeDirect(
             argumentCount, instanceInitializer, register, notUsed, notUsed, notUsed, notUsed)
-        .write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
-    new DexThrow(register).write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+        .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
+    new DexThrow(register)
+        .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index 661cefb..a15e551 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -248,11 +248,14 @@
       ShortBuffer shortBuffer,
       ProgramMethod context,
       GraphLens graphLens,
+      GraphLens codeLens,
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping) {
     int register = 0;
-    new DexConst4(register, 0).write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
-    new DexThrow(register).write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+    new DexConst4(register, 0)
+        .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
+    new DexThrow(register)
+        .write(shortBuffer, context, graphLens, codeLens, mapping, lensCodeRewriter);
   }
 
   @Override
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 8050e50..b84809e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
 import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
@@ -91,6 +92,8 @@
               : IRCodeProvider.createThrowing();
       run(runtimeTypeCheckInfo, codeProvider, executorService, timing);
 
+      assert ArtProfileCompletenessChecker.verify(appView);
+
       // Clear type elements cache after IR building.
       appView.dexItemFactory().clearTypeElementsCache();
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 716a9f3..d827984 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -312,11 +313,23 @@
     DexMethod newMethodReferenceTemplate = getNewMethodReference(representative, needsClassId);
     assert mode.isInitial() || classMethodsBuilder.isFresh(newMethodReferenceTemplate);
 
+    Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
     DexMethod newMethodReference =
         dexItemFactory.createInstanceInitializerWithFreshProto(
             newMethodReferenceTemplate,
             mode.isInitial() ? syntheticArgumentClass.getArgumentClasses() : ImmutableList.of(),
-            classMethodsBuilder::isFresh);
+            classMethodsBuilder::isFresh,
+            usedSyntheticArgumentClasses::set);
+
+    // Amend the art profile collection.
+    if (usedSyntheticArgumentClasses.isSet()) {
+      for (ProgramMethod instanceInitializer : instanceInitializers) {
+        artProfileCollectionAdditions.applyIfContextIsInProfile(
+            instanceInitializer.getReference(),
+            additionsBuilder ->
+                usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
+      }
+    }
 
     // Compute the extra unused null parameters.
     List<ExtraUnusedNullParameter> extraUnusedNullParameters =
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 ec19e83..1074a5c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -9,9 +9,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
+import com.google.common.base.Suppliers;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Supplier;
 
 /**
  * Lets assume we are merging a class A that looks like:
@@ -32,13 +34,13 @@
  */
 public class SyntheticArgumentClass {
 
-  private final List<DexType> syntheticClassTypes;
+  private final List<Supplier<DexType>> syntheticClassTypes;
 
-  private SyntheticArgumentClass(List<DexType> syntheticClassTypes) {
+  private SyntheticArgumentClass(List<Supplier<DexType>> syntheticClassTypes) {
     this.syntheticClassTypes = syntheticClassTypes;
   }
 
-  public List<DexType> getArgumentClasses() {
+  public List<Supplier<DexType>> getArgumentClasses() {
     return syntheticClassTypes;
   }
 
@@ -59,13 +61,22 @@
 
     public SyntheticArgumentClass build(Collection<MergeGroup> mergeGroups) {
       DexProgramClass context = getDeterministicContext(mergeGroups);
-      List<DexType> syntheticArgumentTypes = new ArrayList<>();
+      List<Supplier<DexType>> syntheticArgumentTypes = new ArrayList<>();
       syntheticArgumentTypes.add(
-          synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_1).getType());
+          Suppliers.memoize(
+              () ->
+                  synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
+                      .getType()));
       syntheticArgumentTypes.add(
-          synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_2).getType());
+          Suppliers.memoize(
+              () ->
+                  synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
+                      .getType()));
       syntheticArgumentTypes.add(
-          synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_3).getType());
+          Suppliers.memoize(
+              () ->
+                  synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_3)
+                      .getType()));
       return new SyntheticArgumentClass(syntheticArgumentTypes);
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 7d26bd1..b47b947 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -331,7 +331,7 @@
       for (ProgramMethod oldMethod : methods) {
         artProfileCollectionAdditions.applyIfContextIsInProfile(
             oldMethod.getReference(),
-            additionsBuilder -> additionsBuilder.addRule(newMethodReference));
+            additionsBuilder -> additionsBuilder.addRule(representativeMethod.getReference()));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index a3e5972..1872754 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
@@ -78,10 +80,18 @@
     assert appView.options().isGeneratingDex();
     assert mode.isFinal();
     clazz.forEachProgramInstanceInitializerMatching(
-        method -> !method.getCode().isDexCode(),
+        method -> method.getCode().isCfCode(),
         method -> {
+          GraphLens codeLens = method.getDefinition().getCode().getCodeLens(appView);
+          assert codeLens != appView.codeLens();
+
+          // Convert to dex.
           processMethod(method, converter);
-          assert method.getDefinition().getCode().isDexCode();
+
+          // Recover code lens.
+          Code code = method.getDefinition().getCode();
+          assert code.isDexCode();
+          method.setCode(code.asDexCode().withCodeLens(codeLens), appView);
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 66d04da..a78dc55 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -168,7 +169,9 @@
     timing.begin("[Proto] Post optimize generated extension registry");
     ProgramMethodSet wave =
         ProgramMethodSet.create(this::forEachMethodThatRequiresPostOptimization);
-    OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
+    MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
+    OneTimeMethodProcessor methodProcessor =
+        OneTimeMethodProcessor.create(wave, eventConsumer, appView);
     methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingContext) ->
             converter.processDesugaredMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 104b854..2efe2c3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -224,7 +225,9 @@
       throws ExecutionException {
     timing.begin("[Proto] Post optimize dynamic methods");
     ProgramMethodSet wave = ProgramMethodSet.create(this::forEachDynamicMethod);
-    OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
+    MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
+    OneTimeMethodProcessor methodProcessor =
+        OneTimeMethodProcessor.create(wave, eventConsumer, appView);
     methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingContext) ->
             converter.processDesugaredMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 13f8213..c521bf5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -117,8 +118,7 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     // In debug mode, ArrayPut has a side-effect on the locals.
     if (appView.options().debug) {
       return true;
@@ -158,29 +158,24 @@
     if (!valueType.lessThanOrEqualUpToNullability(memberType, appView)) {
       return true;
     }
-
-    if (array.hasPhiUsers()) {
-      // The array could be used indirectly.
-      return true;
-    }
-
-    // Check that all usages of the array are array stores.
-    for (Instruction user : array.aliasedUsers()) {
-      if (user.isAssume()) {
-        if (user.outValue().hasPhiUsers()) {
-          return true;
-        }
-        continue;
-      }
-      if (!user.isArrayPut() || user.asArrayPut().array().getAliasedValue() != array) {
-        return true;
-      }
-    }
-
     return false;
   }
 
   @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
+    // This modifies the array (or throws).
+    return true;
+  }
+
+  @Override
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    return instructionInstanceCanThrow(appView, code.context())
+        ? DeadInstructionResult.notDead()
+        : DeadInstructionResult.deadIfInValueIsDead(array());
+  }
+
+  @Override
   public boolean identicalAfterRegisterAllocation(
       Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     // We cannot share ArrayPut instructions without knowledge of the type of the array input.
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 0a79887..2daa545 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -662,15 +662,6 @@
     return !phis.isEmpty();
   }
 
-  public boolean hasDeadPhi(AppView<?> appView, IRCode code) {
-    for (Phi phi : phis) {
-      if (phi.isDead(appView, code)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   public List<Phi> getPhis() {
     return phis;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 54710df..b0eb2bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -464,6 +463,10 @@
     return !users.isEmpty() || !phiUsers.isEmpty() || numberOfDebugUsers() > 0;
   }
 
+  public boolean isUnused() {
+    return !isUsed();
+  }
+
   public boolean isAlwaysNull(AppView<?> appView) {
     if (hasLocalInfo()) {
       // Not always null as the value can be changed via the debugger.
@@ -972,73 +975,6 @@
     }
   }
 
-  public boolean isDead(AppView<?> appView, IRCode code) {
-    // Totally unused values are trivially dead.
-    return !isUsed() || isDead(appView, code, Predicates.alwaysFalse());
-  }
-
-  public boolean isDead(AppView<?> appView, IRCode code, Predicate<Instruction> ignoreUser) {
-    // Totally unused values are trivially dead.
-    return !isUsed() || isDead(appView, code, ignoreUser, Sets.newIdentityHashSet());
-  }
-
-  /**
-   * Used to determine if a given value is dead.
-   *
-   * <p>The predicate `ignoreUser` can be used to determine if a given value is dead under the
-   * assumption that the instructions for which `ignoreUser` returns true are also dead.
-   *
-   * <p>One use case of this is when we attempt to determine if a call to {@code <init>()} can be
-   * removed: calls to {@code <init>()} can only be removed if the receiver is dead except for the
-   * constructor call.
-   */
-  protected boolean isDead(
-      AppView<?> appView, IRCode code, Predicate<Instruction> ignoreUser, Set<Value> active) {
-    // Give up when the dependent set of values reach a given threshold (otherwise this fails with
-    // a StackOverflowError on Art003_omnibus_opcodesTest).
-    if (active.size() > 100) {
-      return false;
-    }
-
-    // If the value has debug users we cannot eliminate it since it represents a value in a local
-    // variable that should be visible in the debugger.
-    if (hasDebugUsers()) {
-      return false;
-    }
-    // This is a candidate for a dead value. Guard against looping by adding it to the set of
-    // currently active values.
-    active.add(this);
-    for (Instruction instruction : uniqueUsers()) {
-      if (ignoreUser.test(instruction)) {
-        continue;
-      }
-      DeadInstructionResult result = instruction.canBeDeadCode(appView, code);
-      if (result.isNotDead()) {
-        return false;
-      }
-      if (result.isMaybeDead()) {
-        for (Value valueRequiredToBeDead : result.getValuesRequiredToBeDead()) {
-          if (!active.contains(valueRequiredToBeDead)
-              && !valueRequiredToBeDead.isDead(appView, code, ignoreUser, active)) {
-            return false;
-          }
-        }
-      }
-      Value outValue = instruction.outValue();
-      if (outValue != null
-          && !active.contains(outValue)
-          && !outValue.isDead(appView, code, ignoreUser, active)) {
-        return false;
-      }
-    }
-    for (Phi phi : uniquePhiUsers()) {
-      if (!active.contains(phi) && !phi.isDead(appView, code, ignoreUser, active)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
   public boolean isZero() {
     return isConstant()
         && getConstInstruction().isConstNumber()
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java b/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java
new file mode 100644
index 0000000..73d0a99
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueIsDeadAnalysis.java
@@ -0,0 +1,265 @@
+// Copyright (c) 2023, 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.code;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class ValueIsDeadAnalysis {
+
+  private enum ValueIsDeadResult {
+    DEAD,
+    NOT_DEAD;
+
+    boolean isDead() {
+      return this == DEAD;
+    }
+
+    boolean isNotDead() {
+      return this == NOT_DEAD;
+    }
+  }
+
+  private final AppView<?> appView;
+  private final IRCode code;
+
+  private final Map<Value, ValueIsDeadResult> analysisCache = new IdentityHashMap<>();
+
+  public ValueIsDeadAnalysis(AppView<?> appView, IRCode code) {
+    this.appView = appView;
+    this.code = code;
+  }
+
+  public boolean isDead(Value value) {
+    // Totally unused values are trivially dead.
+    if (value.isUnused()) {
+      return true;
+    }
+    // Create an is-dead dependence graph. If the deadness of value v depends on value u being dead,
+    // a directed edge [v -> u] is added to the graph.
+    //
+    // This graph serves two purposes:
+    // 1) If the analysis finds that `u` is *not* dead, then using the graph we can find all the
+    //    values whose deadness depend (directly or indirectly) on `u` being dead, and mark them as
+    //    being *not* dead in the analysis cache.
+    // 2) If the analysis finds that `u` *is* dead, we can remove the node from the dependence graph
+    //    (as it is necessarily a leaf), and repeatedly mark direct and indirect predecessors of `u`
+    //    that have now become leaves as being dead in the analysis cache.
+    WorkList<Value> worklist = WorkList.newIdentityWorkList(value);
+    BooleanBox foundCycle = new BooleanBox();
+    Value notDeadWitness = findNotDeadWitness(worklist, foundCycle);
+    boolean isDead = Objects.isNull(notDeadWitness);
+    if (isDead) {
+      if (foundCycle.isTrue()) {
+        for (Value deadValue : worklist.getSeenSet()) {
+          recordValueIsDead(deadValue);
+        }
+      } else {
+        assert worklist.getSeenSet().stream()
+            .allMatch(deadValue -> analysisCache.get(deadValue) == ValueIsDeadResult.DEAD);
+      }
+    }
+    return isDead;
+  }
+
+  public boolean hasDeadPhi(BasicBlock block) {
+    return Iterables.any(block.getPhis(), this::isDead);
+  }
+
+  private Value findNotDeadWitness(WorkList<Value> worklist, BooleanBox foundCycle) {
+    DependenceGraph dependenceGraph = new DependenceGraph();
+    while (worklist.hasNext()) {
+      Value value = worklist.next();
+
+      // The first time we visit a value we have not yet added any outgoing edges to the dependence
+      // graph.
+      assert dependenceGraph.isLeaf(value);
+
+      // Lookup if we have already analyzed the deadness of this value.
+      ValueIsDeadResult cacheResult = analysisCache.get(value);
+      if (cacheResult != null) {
+        // If it is dead, then continue the search for a non-dead dependent. Otherwise this value is
+        // a witness that the analysis failed.
+        if (cacheResult.isDead()) {
+          continue;
+        } else {
+          recordDependentsAreNotDead(value, dependenceGraph);
+          return value;
+        }
+      }
+
+      // If the value has debug users we cannot eliminate it since it represents a value in a local
+      // variable that should be visible in the debugger.
+      if (value.hasDebugUsers()) {
+        recordValueAndDependentsAreNotDead(value, dependenceGraph);
+        return value;
+      }
+
+      Set<Value> valuesRequiredToBeDead = new LinkedHashSet<>(value.uniquePhiUsers());
+      for (Instruction instruction : value.uniqueUsers()) {
+        DeadInstructionResult result = instruction.canBeDeadCode(appView, code);
+        if (result.isNotDead()) {
+          recordValueAndDependentsAreNotDead(value, dependenceGraph);
+          return value;
+        }
+        if (result.isMaybeDead()) {
+          result.getValuesRequiredToBeDead().forEach(valuesRequiredToBeDead::add);
+        }
+        if (instruction.hasOutValue()) {
+          valuesRequiredToBeDead.add(instruction.outValue());
+        }
+      }
+
+      Iterator<Value> valuesRequiredToBeDeadIterator = valuesRequiredToBeDead.iterator();
+      while (valuesRequiredToBeDeadIterator.hasNext()) {
+        Value valueRequiredToBeDead = valuesRequiredToBeDeadIterator.next();
+        if (hasProvenThatValueIsNotDead(valueRequiredToBeDead)) {
+          recordValueAndDependentsAreNotDead(value, dependenceGraph);
+          return value;
+        }
+        if (!needsToProveThatValueIsDead(value, valueRequiredToBeDead)) {
+          valuesRequiredToBeDeadIterator.remove();
+        }
+      }
+
+      if (valuesRequiredToBeDead.isEmpty()) {
+        // We have now proven that this value is dead.
+        recordValueIsDeadAndPropagateToDependents(value, dependenceGraph);
+      } else {
+        // Record the current value as a dependent of each value required to be dead.
+        for (Value valueRequiredToBeDead : valuesRequiredToBeDead) {
+          dependenceGraph.addDependenceEdge(value, valueRequiredToBeDead);
+          foundCycle.or(worklist.isSeen(valueRequiredToBeDead));
+        }
+
+        // Continue the analysis of the dependents.
+        worklist.addIfNotSeen(valuesRequiredToBeDead);
+      }
+    }
+    return null;
+  }
+
+  private boolean hasProvenThatValueIsNotDead(Value valueRequiredToBeDead) {
+    return analysisCache.get(valueRequiredToBeDead) == ValueIsDeadResult.NOT_DEAD;
+  }
+
+  private boolean needsToProveThatValueIsDead(Value value, Value valueRequiredToBeDead) {
+    // No need to record that the deadness of a values relies on its own removal.
+    assert !hasProvenThatValueIsNotDead(valueRequiredToBeDead);
+    return valueRequiredToBeDead != value && !analysisCache.containsKey(valueRequiredToBeDead);
+  }
+
+  private void recordValueIsDeadAndPropagateToDependents(
+      Value value, DependenceGraph dependenceGraph) {
+    WorkList<Value> worklist = WorkList.newIdentityWorkList(value);
+    while (worklist.hasNext()) {
+      Value current = worklist.next();
+      recordValueIsDead(current);
+
+      // This value is now proven to be dead, thus there is no need to keep track of its successors.
+      dependenceGraph.unlinkSuccessors(current);
+
+      // Continue processing of new leaves.
+      for (Value dependent : dependenceGraph.removeLeaf(current)) {
+        if (dependenceGraph.isLeaf(dependent)) {
+          worklist.addIfNotSeen(dependent);
+        }
+      }
+    }
+  }
+
+  private void recordValueIsDead(Value value) {
+    ValueIsDeadResult existingResult = analysisCache.put(value, ValueIsDeadResult.DEAD);
+    assert existingResult == null || existingResult.isDead();
+  }
+
+  private void recordValueAndDependentsAreNotDead(Value value, DependenceGraph dependenceGraph) {
+    recordValueIsNotDead(value, dependenceGraph);
+    recordDependentsAreNotDead(value, dependenceGraph);
+  }
+
+  private void recordValueIsNotDead(Value value, DependenceGraph dependenceGraph) {
+    // This value is now proven to be dead, thus there is no need to keep track of its successors.
+    dependenceGraph.unlinkSuccessors(value);
+    ValueIsDeadResult existingResult = analysisCache.put(value, ValueIsDeadResult.NOT_DEAD);
+    assert existingResult == null || existingResult.isNotDead();
+  }
+
+  private void recordDependentsAreNotDead(Value value, DependenceGraph dependenceGraph) {
+    WorkList<Value> worklist = WorkList.newIdentityWorkList(value);
+    while (worklist.hasNext()) {
+      Value current = worklist.next();
+      for (Value dependent : dependenceGraph.removeLeaf(current)) {
+        recordValueIsNotDead(dependent, dependenceGraph);
+        worklist.addIfNotSeen(dependent);
+      }
+    }
+  }
+
+  private static class DependenceGraph {
+
+    private final Map<Value, Set<Value>> successors = new IdentityHashMap<>();
+    private final Map<Value, Set<Value>> predecessors = new IdentityHashMap<>();
+
+    /**
+     * Records that the removal of {@param value} depends on the removal of {@param
+     * valueRequiredToBeDead} by adding an edge from {@param value} to {@param
+     * valueRequiredToBeDead} in this graph.
+     */
+    void addDependenceEdge(Value value, Value valueRequiredToBeDead) {
+      successors
+          .computeIfAbsent(value, ignoreKey(Sets::newIdentityHashSet))
+          .add(valueRequiredToBeDead);
+      predecessors
+          .computeIfAbsent(valueRequiredToBeDead, ignoreKey(Sets::newIdentityHashSet))
+          .add(value);
+    }
+
+    Set<Value> removeLeaf(Value value) {
+      assert isLeaf(value);
+      Set<Value> dependents = MapUtils.removeOrDefault(predecessors, value, Collections.emptySet());
+      for (Value dependent : dependents) {
+        Set<Value> dependentSuccessors = successors.get(dependent);
+        boolean removed = dependentSuccessors.remove(value);
+        assert removed;
+        if (dependentSuccessors.isEmpty()) {
+          successors.remove(dependent);
+        }
+      }
+      return dependents;
+    }
+
+    void unlinkSuccessors(Value value) {
+      Set<Value> valueSuccessors =
+          MapUtils.removeOrDefault(successors, value, Collections.emptySet());
+      for (Value successor : valueSuccessors) {
+        Set<Value> successorPredecessors = predecessors.get(successor);
+        boolean removed = successorPredecessors.remove(value);
+        assert removed;
+        if (successorPredecessors.isEmpty()) {
+          predecessors.remove(successor);
+        }
+      }
+    }
+
+    boolean isLeaf(Value value) {
+      return !successors.containsKey(value);
+    }
+  }
+}
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 23e1ab3..3dfe55f 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
@@ -57,6 +57,7 @@
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.PhiOptimizations;
 import com.android.tools.r8.ir.optimize.peepholes.BasicBlockMuncher;
+import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -143,17 +144,22 @@
     this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
   }
 
-  public CfCode build(DeadCodeRemover deadCodeRemover) {
-    code.traceBlocks();
-    computeInitializers();
+  public CfCode build(DeadCodeRemover deadCodeRemover, Timing timing) {
+    timing.time("Trace blocks", code::traceBlocks);
+    timing.time("Compute Initializers", () -> computeInitializers());
+    timing.begin("Compute verification types");
     TypeVerificationHelper typeVerificationHelper = new TypeVerificationHelper(appView, code);
     typeVerificationHelper.computeVerificationTypes();
+    timing.end();
     assert deadCodeRemover.verifyNoDeadCode(code);
-    rewriteNots();
+    timing.time("Rewrite nots", this::rewriteNots);
+    timing.begin("Insert loads and stores");
     LoadStoreHelper loadStoreHelper = new LoadStoreHelper(appView, code, typeVerificationHelper);
     loadStoreHelper.insertLoadsAndStores();
+    timing.end();
     // Run optimizations on phis and basic blocks in a fixpoint.
     if (appView.options().enableLoadStoreOptimization) {
+      timing.begin("Load store optimizations (BasicBlockMunching)");
       PhiOptimizations phiOptimizations = new PhiOptimizations();
       boolean reachedFixpoint = false;
       phiOptimizations.optimize(code);
@@ -161,23 +167,33 @@
         BasicBlockMuncher.optimize(code, appView.options());
         reachedFixpoint = !phiOptimizations.optimize(code);
       }
+      timing.end();
     }
     assert code.isConsistentSSA(appView);
     // Insert reads for uninitialized read blocks to ensure correct stack maps.
+    timing.begin("Insert uninitialized local reads");
     Set<UninitializedThisLocalRead> uninitializedThisLocalReads =
         insertUninitializedThisLocalReads();
+    timing.end();
+    timing.begin("Register allocation");
     registerAllocator = new CfRegisterAllocator(appView, code, typeVerificationHelper);
     registerAllocator.allocateRegisters();
+    timing.end();
     // Remove any uninitializedThisLocalRead now that the register allocation will preserve this
     // for instance initializers.
+    timing.begin("Remove uninitialized local reads");
     if (!uninitializedThisLocalReads.isEmpty()) {
       for (UninitializedThisLocalRead uninitializedThisLocalRead : uninitializedThisLocalReads) {
         uninitializedThisLocalRead.getBlock().removeInstruction(uninitializedThisLocalRead);
       }
     }
+    timing.end();
 
+    timing.begin("Insert phi moves");
     loadStoreHelper.insertPhiMoves(registerAllocator);
+    timing.end();
 
+    timing.begin("BasicBlock peephole optimizations");
     if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) {
       for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
         CodeRewriter.collapseTrivialGotos(appView, code);
@@ -186,12 +202,17 @@
             code, registerAllocator, SUFFIX_SHARING_OVERHEAD);
       }
     }
+    timing.end();
 
-    rewriteIincPatterns();
+    timing.time("Rewrite Iinc patterns", this::rewriteIincPatterns);
 
     CodeRewriter.collapseTrivialGotos(appView, code);
+    timing.begin("Remove redundant debug positions");
     DexBuilder.removeRedundantDebugPositions(code);
+    timing.end();
+    timing.begin("Build CF Code");
     CfCode code = buildCfCode();
+    timing.end();
     assert verifyInvokeInterface(code, appView);
     assert code.getOrComputeStackMapStatus(method, appView, appView.graphLens())
         .isValidOrNotPresent();
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 5dc2584..7b3641f 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
@@ -34,18 +34,16 @@
 public abstract class ClassConverter {
 
   protected final AppView<?> appView;
-  private final ArtProfileCollectionAdditions artProfileCollectionAdditions;
-  private final IRConverter converter;
+  private final PrimaryD8L8IRConverter converter;
   private final D8MethodProcessor methodProcessor;
   private final InterfaceProcessor interfaceProcessor;
 
   ClassConverter(
       AppView<?> appView,
-      IRConverter converter,
+      PrimaryD8L8IRConverter converter,
       D8MethodProcessor methodProcessor,
       InterfaceProcessor interfaceProcessor) {
     this.appView = appView;
-    this.artProfileCollectionAdditions = ArtProfileCollectionAdditions.create(appView);
     this.converter = converter;
     this.methodProcessor = methodProcessor;
     this.interfaceProcessor = interfaceProcessor;
@@ -53,7 +51,7 @@
 
   public static ClassConverter create(
       AppView<?> appView,
-      IRConverter converter,
+      PrimaryD8L8IRConverter converter,
       D8MethodProcessor methodProcessor,
       InterfaceProcessor interfaceProcessor) {
     return appView.options().desugarSpecificOptions().allowAllDesugaredInput
@@ -66,7 +64,6 @@
       throws ExecutionException {
     ClassConverterResult.Builder resultBuilder = ClassConverterResult.builder();
     internalConvertClasses(resultBuilder, executorService);
-    artProfileCollectionAdditions.commit(appView);
     notifyAllClassesConverted();
     return resultBuilder.build();
   }
@@ -117,9 +114,10 @@
       ClassConverterResult.Builder resultBuilder, ExecutorService executorService)
       throws ExecutionException {
     Collection<DexProgramClass> classes = appView.appInfo().classes();
-
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        methodProcessor.getArtProfileCollectionAdditions();
     CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer =
-        new CfClassSynthesizerDesugaringEventConsumer();
+        CfClassSynthesizerDesugaringEventConsumer.create(artProfileCollectionAdditions);
     converter.classSynthesisDesugaring(executorService, classSynthesizerEventConsumer);
     if (!classSynthesizerEventConsumer.getSynthesizedClasses().isEmpty()) {
       classes =
@@ -132,8 +130,7 @@
     CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumerForPrepareStep =
         CfInstructionDesugaringEventConsumer.createForD8(
             appView, artProfileCollectionAdditions, resultBuilder, methodProcessor);
-    converter.prepareDesugaringForD8(
-        instructionDesugaringEventConsumerForPrepareStep, executorService);
+    converter.prepareDesugaring(instructionDesugaringEventConsumerForPrepareStep, executorService);
     assert instructionDesugaringEventConsumerForPrepareStep.verifyNothingToFinalize();
 
     // When adding nest members to the wave we must do so deterministically.
@@ -236,7 +233,7 @@
 
     DefaultClassConverter(
         AppView<?> appView,
-        IRConverter converter,
+        PrimaryD8L8IRConverter converter,
         D8MethodProcessor methodProcessor,
         InterfaceProcessor interfaceProcessor) {
       super(appView, converter, methodProcessor, interfaceProcessor);
@@ -260,7 +257,7 @@
 
     LibraryDesugaredClassConverter(
         AppView<?> appView,
-        IRConverter converter,
+        PrimaryD8L8IRConverter converter,
         D8MethodProcessor methodProcessor,
         InterfaceProcessor interfaceProcessor) {
       super(appView, converter, methodProcessor, interfaceProcessor);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index c6b278c..7a04167 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -25,7 +26,9 @@
 
 public class D8MethodProcessor extends MethodProcessor {
 
-  private final IRConverter converter;
+  private final ArtProfileCollectionAdditions artProfileCollectionAdditions;
+  private final PrimaryD8L8IRConverter converter;
+  private final MethodProcessorEventConsumer eventConsumer;
   private final ExecutorService executorService;
   private final Set<DexType> scheduled = Sets.newIdentityHashSet();
 
@@ -41,8 +44,13 @@
 
   private ProcessorContext processorContext;
 
-  public D8MethodProcessor(IRConverter converter, ExecutorService executorService) {
+  public D8MethodProcessor(
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
+      PrimaryD8L8IRConverter converter,
+      ExecutorService executorService) {
+    this.artProfileCollectionAdditions = artProfileCollectionAdditions;
     this.converter = converter;
+    this.eventConsumer = MethodProcessorEventConsumer.create(artProfileCollectionAdditions);
     this.executorService = executorService;
     this.processorContext = converter.appView.createProcessorContext();
   }
@@ -61,6 +69,15 @@
     return processorContext.createMethodProcessingContext(method);
   }
 
+  public ArtProfileCollectionAdditions getArtProfileCollectionAdditions() {
+    return artProfileCollectionAdditions;
+  }
+
+  @Override
+  public MethodProcessorEventConsumer getEventConsumer() {
+    return eventConsumer;
+  }
+
   @Override
   public boolean isProcessedConcurrently(ProgramMethod method) {
     // In D8 all methods are considered independently compiled.
@@ -116,9 +133,8 @@
             executorService));
   }
 
-  public D8MethodProcessor scheduleDesugaredMethodsForProcessing(Iterable<ProgramMethod> methods) {
+  public void scheduleDesugaredMethodsForProcessing(Iterable<ProgramMethod> methods) {
     methods.forEach(this::scheduleDesugaredMethodForProcessing);
-    return this;
   }
 
   @Override
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 fbcfcba..6d6edbf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -3,50 +3,28 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
-import static com.android.tools.r8.ir.desugar.lambda.D8LambdaDesugaring.rewriteEnclosingLambdaMethodAttributes;
-
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.fieldaccess.FieldAccessAnalysis;
-import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringCollection;
-import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
-import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection;
-import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
-import com.android.tools.r8.ir.desugar.ProgramAdditions;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
-import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
-import com.android.tools.r8.ir.desugar.itf.L8InnerOuterAttributeEraser;
-import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
-import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
@@ -61,11 +39,11 @@
 import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.InstanceInitializerOutliner;
 import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
+import com.android.tools.r8.ir.optimize.api.InstanceInitializerOutliner;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
@@ -87,10 +65,8 @@
 import com.android.tools.r8.lightir.LIRCode;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
-import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorIROptimizer;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
@@ -101,15 +77,16 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.NeverMergeGroup;
 import com.android.tools.r8.utils.LazyBox;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -120,32 +97,32 @@
 
   public final AppView<?> appView;
 
-  private final Timing timing;
+  protected final Timing timing;
   public final Outliner outliner;
   private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization;
-  private final CfInstructionDesugaringCollection instructionDesugaring;
-  private final FieldAccessAnalysis fieldAccessAnalysis;
-  private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
-  private final StringOptimizer stringOptimizer;
-  private final IdempotentFunctionCallCanonicalizer idempotentFunctionCallCanonicalizer;
+  protected final CfInstructionDesugaringCollection instructionDesugaring;
+  protected final FieldAccessAnalysis fieldAccessAnalysis;
+  protected final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
+  protected final StringOptimizer stringOptimizer;
+  protected final IdempotentFunctionCallCanonicalizer idempotentFunctionCallCanonicalizer;
   private final ClassInliner classInliner;
-  private final InternalOptions options;
-  private final CfgPrinter printer;
+  protected final InternalOptions options;
+  protected final CfgPrinter printer;
   public final CodeRewriter codeRewriter;
   public final AssertionErrorTwoArgsConstructorRewriter assertionErrorTwoArgsConstructorRewriter;
   private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover();
   public final MemberValuePropagation<?> memberValuePropagation;
   private final LensCodeRewriter lensCodeRewriter;
-  private final Inliner inliner;
-  private final IdentifierNameStringMarker identifierNameStringMarker;
+  protected final Inliner inliner;
+  protected final IdentifierNameStringMarker identifierNameStringMarker;
   private final Devirtualizer devirtualizer;
-  private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
+  protected final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
   private final StringSwitchRemover stringSwitchRemover;
   private final TypeChecker typeChecker;
-  private final ServiceLoaderRewriter serviceLoaderRewriter;
+  protected final ServiceLoaderRewriter serviceLoaderRewriter;
   private final EnumValueOptimizer enumValueOptimizer;
-  private final EnumUnboxer enumUnboxer;
-  private final InstanceInitializerOutliner instanceInitializerOutliner;
+  protected final EnumUnboxer enumUnboxer;
+  protected final InstanceInitializerOutliner instanceInitializerOutliner;
 
   public final AssumeInserter assumeInserter;
   private final DynamicTypeOptimization dynamicTypeOptimization;
@@ -155,16 +132,16 @@
 
   private final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
 
-  private final OptimizationFeedbackDelayed delayedOptimizationFeedback =
+  protected final OptimizationFeedbackDelayed delayedOptimizationFeedback =
       new OptimizationFeedbackDelayed();
-  private final OptimizationFeedback simpleOptimizationFeedback =
+  protected final OptimizationFeedback simpleOptimizationFeedback =
       OptimizationFeedbackSimple.getInstance();
-  private DexString highestSortingString;
+  protected DexString highestSortingString;
 
-  private List<Action> onWaveDoneActions = null;
-  private final Set<DexMethod> prunedMethodsInWave = Sets.newIdentityHashSet();
+  protected List<Action> onWaveDoneActions = null;
+  protected final Set<DexMethod> prunedMethodsInWave = Sets.newIdentityHashSet();
 
-  private final NeverMergeGroup<DexString> neverMerge;
+  protected final NeverMergeGroup<DexString> neverMerge;
   // Use AtomicBoolean to satisfy TSAN checking (see b/153714743).
   AtomicBoolean seenNotNeverMergePrefix = new AtomicBoolean();
   AtomicBoolean seenNeverMergePrefix = new AtomicBoolean();
@@ -328,239 +305,6 @@
     return inliner;
   }
 
-  private void synthesizeBridgesForNestBasedAccessesOnClasspath(
-      D8MethodProcessor methodProcessor, ExecutorService executorService)
-      throws ExecutionException {
-    instructionDesugaring.withD8NestBasedAccessDesugaring(
-        d8NestBasedAccessDesugaring ->
-            d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
-                methodProcessor, executorService));
-    methodProcessor.awaitMethodProcessing();
-  }
-
-  private void reportNestDesugarDependencies() {
-    instructionDesugaring.withD8NestBasedAccessDesugaring(
-        D8NestBasedAccessDesugaring::reportDesugarDependencies);
-  }
-
-  private void clearNestAttributes() {
-    instructionDesugaring.withD8NestBasedAccessDesugaring(
-        D8NestBasedAccessDesugaring::clearNestAttributes);
-  }
-
-  private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
-    if (covariantReturnTypeAnnotationTransformer != null) {
-      covariantReturnTypeAnnotationTransformer.process(builder);
-    }
-  }
-
-  public void convert(AppView<AppInfo> appView, ExecutorService executor)
-      throws ExecutionException {
-    LambdaDeserializationMethodRemover.run(appView);
-    workaroundAbstractMethodOnNonAbstractClassVerificationBug(executor);
-    DexApplication application = appView.appInfo().app();
-    D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executor);
-    InterfaceProcessor interfaceProcessor = InterfaceProcessor.create(appView);
-
-    timing.begin("IR conversion");
-
-    convertClasses(methodProcessor, interfaceProcessor, executor);
-
-    reportNestDesugarDependencies();
-    clearNestAttributes();
-
-    if (instanceInitializerOutliner != null) {
-      processSimpleSynthesizeMethods(instanceInitializerOutliner.getSynthesizedMethods(), executor);
-    }
-    if (assertionErrorTwoArgsConstructorRewriter != null) {
-      processSimpleSynthesizeMethods(
-          assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executor);
-    }
-
-    application = commitPendingSyntheticItemsD8(appView, application);
-
-    postProcessingDesugaringForD8(methodProcessor, interfaceProcessor, executor);
-
-    application = commitPendingSyntheticItemsD8(appView, application);
-
-    // Build a new application with jumbo string info,
-    Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
-
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
-      new L8InnerOuterAttributeEraser(appView).run();
-    }
-    processCovariantReturnTypeAnnotations(builder);
-
-    timing.end();
-
-    application = builder.build();
-    appView.setAppInfo(
-        new AppInfo(
-            appView.appInfo().getSyntheticItems().commit(application),
-            appView.appInfo().getMainDexInfo()));
-  }
-
-  private DexApplication commitPendingSyntheticItemsD8(
-      AppView<AppInfo> appView, DexApplication application) {
-    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-      appView.setAppInfo(
-          new AppInfo(
-              appView.appInfo().getSyntheticItems().commit(application),
-              appView.appInfo().getMainDexInfo()));
-      application = appView.appInfo().app();
-    }
-    return application;
-  }
-
-  private static void commitPendingSyntheticItemsR8(AppView<AppInfoWithLiveness> appView) {
-    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-      appView.setAppInfo(
-          appView
-              .appInfo()
-              .rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
-    }
-  }
-
-  public void classSynthesisDesugaring(
-      ExecutorService executorService,
-      CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer)
-      throws ExecutionException {
-    CfClassSynthesizerDesugaringCollection.create(appView)
-        .synthesizeClasses(executorService, classSynthesizerEventConsumer);
-  }
-
-  private void postProcessingDesugaringForD8(
-      D8MethodProcessor methodProcessor,
-      InterfaceProcessor interfaceProcessor,
-      ExecutorService executorService)
-      throws ExecutionException {
-    ArtProfileCollectionAdditions artProfileCollectionAdditions =
-        ArtProfileCollectionAdditions.create(appView);
-    CfPostProcessingDesugaringEventConsumer eventConsumer =
-        CfPostProcessingDesugaringEventConsumer.createForD8(
-            artProfileCollectionAdditions, methodProcessor, instructionDesugaring);
-    methodProcessor.newWave();
-    InterfaceMethodProcessorFacade interfaceDesugaring =
-        instructionDesugaring.getInterfaceMethodPostProcessingDesugaringD8(
-            ExcludeDexResources, interfaceProcessor);
-    CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring)
-        .postProcessingDesugaring(
-            appView.appInfo().classes(), m -> true, eventConsumer, executorService);
-    methodProcessor.awaitMethodProcessing();
-    eventConsumer.finalizeDesugaring();
-    artProfileCollectionAdditions.commit(appView);
-  }
-
-  private void convertClasses(
-      D8MethodProcessor methodProcessor,
-      InterfaceProcessor interfaceProcessor,
-      ExecutorService executorService)
-      throws ExecutionException {
-    ClassConverterResult classConverterResult =
-        ClassConverter.create(appView, this, methodProcessor, interfaceProcessor)
-            .convertClasses(executorService);
-
-    // The synthesis of accessibility bridges in nest based access desugaring will schedule and
-    // await the processing of synthesized methods.
-    synthesizeBridgesForNestBasedAccessesOnClasspath(methodProcessor, executorService);
-
-    // There should be no outstanding method processing.
-    methodProcessor.verifyNoPendingMethodProcessing();
-
-    rewriteEnclosingLambdaMethodAttributes(
-        appView, classConverterResult.getForcefullyMovedLambdaMethods());
-
-    instructionDesugaring.withDesugaredLibraryAPIConverter(
-        DesugaredLibraryAPIConverter::generateTrackingWarnings);
-  }
-
-  public void prepareDesugaringForD8(
-      CfInstructionDesugaringEventConsumer desugaringEventConsumer, ExecutorService executorService)
-      throws ExecutionException {
-    // Prepare desugaring by collecting all the synthetic methods required on program classes.
-    ProgramAdditions programAdditions = new ProgramAdditions();
-    ThreadUtils.processItems(
-        appView.appInfo().classes(),
-        clazz -> {
-          clazz.forEachProgramMethodMatching(
-              method -> method.hasCode() && method.getCode().isCfCode(),
-              method ->
-                  instructionDesugaring.prepare(method, desugaringEventConsumer, programAdditions));
-        },
-        executorService);
-    programAdditions.apply(executorService);
-  }
-
-  void convertMethods(
-      DexProgramClass clazz,
-      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
-      D8MethodProcessor methodProcessor,
-      InterfaceProcessor interfaceProcessor) {
-    // When converting all methods on a class always convert <clinit> first.
-    ProgramMethod classInitializer = clazz.getProgramClassInitializer();
-
-    // TODO(b/179755192): We currently need to copy the class' methods, to avoid a
-    //  ConcurrentModificationException from the insertion of methods due to invoke-special
-    //  desugaring. By building up waves of methods in the class converter, we would not need to
-    //  iterate the methods of a class during while its methods are being processed, which avoids
-    //  the need to copy the method list.
-    List<ProgramMethod> methods = ListUtils.newArrayList(clazz::forEachProgramMethod);
-    if (classInitializer != null) {
-      methodProcessor.processMethod(classInitializer, desugaringEventConsumer);
-    }
-
-    for (ProgramMethod method : methods) {
-      if (!method.getDefinition().isClassInitializer()) {
-        methodProcessor.processMethod(method, desugaringEventConsumer);
-        if (interfaceProcessor != null) {
-          interfaceProcessor.processMethod(method, desugaringEventConsumer);
-        }
-      }
-    }
-
-    // The class file version is downgraded after compilation. Some of the desugaring might need
-    // the initial class file version to determine how far a method can be downgraded.
-    if (options.isGeneratingClassFiles() && clazz.hasClassFileVersion()) {
-      clazz.downgradeInitialClassFileVersion(
-          appView.options().classFileVersionAfterDesugaring(clazz.getInitialClassFileVersion()));
-    }
-  }
-
-  void convertMethod(
-      ProgramMethod method,
-      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    DexEncodedMethod definition = method.getDefinition();
-    if (options.isGeneratingClassFiles() && definition.hasClassFileVersion()) {
-      definition.downgradeClassFileVersion(
-          appView.options().classFileVersionAfterDesugaring(definition.getClassFileVersion()));
-    }
-    if (definition.getCode() == null) {
-      return;
-    }
-    if (!options.methodMatchesFilter(definition)) {
-      return;
-    }
-    checkPrefixMerging(method);
-    if (options.isGeneratingClassFiles()
-        || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
-      // We do not process in call graph order, so anything could be a leaf.
-      rewriteNonDesugaredCode(
-          method,
-          desugaringEventConsumer,
-          simpleOptimizationFeedback,
-          methodProcessor,
-          methodProcessingContext);
-    } else {
-      assert definition.getCode().isDexCode();
-    }
-    if (!options.isGeneratingClassFiles()) {
-      updateHighestSortingStrings(definition);
-    }
-  }
-
   private boolean needsIRConversion(ProgramMethod method) {
     if (method.getDefinition().getCode().isThrowNullCode()) {
       return false;
@@ -575,90 +319,7 @@
     return !options.isGeneratingClassFiles();
   }
 
-  private void checkPrefixMerging(ProgramMethod method) {
-    if (!appView.options().enableNeverMergePrefixes) {
-      return;
-    }
-    DexString descriptor = method.getHolderType().descriptor;
-    for (DexString neverMergePrefix : neverMerge.getPrefixes()) {
-      if (descriptor.startsWith(neverMergePrefix)) {
-        seenNeverMergePrefix.getAndSet(true);
-      } else {
-        for (DexString exceptionPrefix : neverMerge.getExceptionPrefixes()) {
-          if (!descriptor.startsWith(exceptionPrefix)) {
-            seenNotNeverMergePrefix.getAndSet(true);
-            break;
-          }
-        }
-      }
-      // Don't mix.
-      // TODO(b/168001352): Consider requiring that no 'never merge' prefix is ever seen as a
-      //  passthrough object.
-      if (seenNeverMergePrefix.get() && seenNotNeverMergePrefix.get()) {
-        StringBuilder message = new StringBuilder();
-        message
-            .append("Merging DEX file containing classes with prefix")
-            .append(neverMerge.getPrefixes().size() > 1 ? "es " : " ");
-        for (int i = 0; i < neverMerge.getPrefixes().size(); i++) {
-          message
-              .append("'")
-              .append(neverMerge.getPrefixes().get(i).toString().substring(1).replace('/', '.'))
-              .append("'")
-              .append(i < neverMerge.getPrefixes().size() - 1 ? ", " : "");
-        }
-        if (!neverMerge.getExceptionPrefixes().isEmpty()) {
-          message
-              .append(" with other classes, except classes with prefix")
-              .append(neverMerge.getExceptionPrefixes().size() > 1 ? "es " : " ");
-          for (int i = 0; i < neverMerge.getExceptionPrefixes().size(); i++) {
-            message
-                .append("'")
-                .append(
-                    neverMerge
-                        .getExceptionPrefixes()
-                        .get(i)
-                        .toString()
-                        .substring(1)
-                        .replace('/', '.'))
-                .append("'")
-                .append(i < neverMerge.getExceptionPrefixes().size() - 1 ? ", " : "");
-          }
-          message.append(",");
-        } else {
-          message.append(" with classes with any other prefixes");
-        }
-        message.append(" is not allowed: ");
-        boolean first = true;
-        int limit = 11;
-        for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
-          if (!clazz.type.descriptor.startsWith(neverMergePrefix)) {
-            boolean hasExceptionPrefix = false;
-            for (DexString exceptionPrefix : neverMerge.getExceptionPrefixes()) {
-              hasExceptionPrefix =
-                  hasExceptionPrefix | clazz.type.descriptor.startsWith(exceptionPrefix);
-            }
-            if (hasExceptionPrefix) {
-              continue;
-            }
-            if (limit-- < 0) {
-              message.append("..");
-              break;
-            }
-            if (first) {
-              first = false;
-            } else {
-              message.append(", ");
-            }
-            message.append(clazz.type);
-          }
-        }
-        message.append(".");
-        throw new CompilationError(message.toString());
-      }
-    }
-  }
-
-  private void workaroundAbstractMethodOnNonAbstractClassVerificationBug(
+  protected void workaroundAbstractMethodOnNonAbstractClassVerificationBug(
       ExecutorService executorService) throws ExecutionException {
     if (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
       return;
@@ -675,225 +336,6 @@
         executorService);
   }
 
-  public DexApplication optimize(
-      AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
-      throws ExecutionException {
-    // Desugaring happens in the enqueuer.
-    assert instructionDesugaring.isEmpty();
-
-    workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService);
-
-    // The process is in two phases in general.
-    // 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
-    //    reprocessing IR code of methods, e.g., outlining, double-inlining, class staticizer, etc.
-    //    - a side effect is candidates for those optimizations are identified.
-    // 2) Revisit DexEncodedMethods for the collected candidates.
-
-    printPhase("Primary optimization pass");
-
-    GraphLens graphLensForPrimaryOptimizationPass = appView.graphLens();
-
-    // Setup optimizations for the primary optimization pass.
-    appView.withArgumentPropagator(
-        argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing));
-    enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
-    outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
-
-    if (fieldAccessAnalysis != null) {
-      fieldAccessAnalysis.fieldAssignmentTracker().initialize();
-    }
-
-    // Process the application identifying outlining candidates.
-    OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
-    PostMethodProcessor.Builder postMethodProcessorBuilder =
-        new PostMethodProcessor.Builder(graphLensForPrimaryOptimizationPass);
-    {
-      timing.begin("Build primary method processor");
-      PrimaryMethodProcessor primaryMethodProcessor =
-          PrimaryMethodProcessor.create(appView.withLiveness(), executorService, timing);
-      timing.end();
-      timing.begin("IR conversion phase 1");
-      assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
-      primaryMethodProcessor.forEachMethod(
-          (method, methodProcessingContext) ->
-              processDesugaredMethod(
-                  method, feedback, primaryMethodProcessor, methodProcessingContext),
-          this::waveStart,
-          this::waveDone,
-          timing,
-          executorService);
-      lastWaveDone(postMethodProcessorBuilder, executorService);
-      assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
-      timing.end();
-    }
-
-    // The field access info collection is not maintained during IR processing.
-    appView.appInfo().withLiveness().getFieldAccessInfoCollection().destroyAccessContexts();
-
-    // Assure that no more optimization feedback left after primary processing.
-    assert feedback.noUpdatesLeft();
-    appView.setAllCodeProcessed();
-
-    // All the code has been processed so the rewriting required by the lenses is done everywhere,
-    // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
-    // lenses with code rewriting are added.
-    appView.clearCodeRewritings();
-
-    // Commit synthetics from the primary optimization pass.
-    commitPendingSyntheticItemsR8(appView);
-
-    // Post processing:
-    //   1) Second pass for methods whose collected call site information become more precise.
-    //   2) Second inlining pass for dealing with double inline callers.
-    printPhase("Post optimization pass");
-
-    // Analyze the data collected by the argument propagator, use the analysis result to update
-    // the parameter optimization infos, and rewrite the application.
-    // TODO(b/199237357): Automatically rewrite state when lens changes.
-    enumUnboxer.rewriteWithLens();
-    outliner.rewriteWithLens();
-    appView.withArgumentPropagator(
-        argumentPropagator ->
-            argumentPropagator.tearDownCodeScanner(
-                this, postMethodProcessorBuilder, executorService, timing));
-
-    if (libraryMethodOverrideAnalysis != null) {
-      libraryMethodOverrideAnalysis.finish();
-    }
-
-    if (!options.debug) {
-      new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
-          .run(executorService, feedback, timing);
-    }
-
-    outliner.rewriteWithLens();
-    enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback);
-    appView.unboxedEnums().checkEnumsUnboxed(appView);
-
-    GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
-
-    outliner.rewriteWithLens();
-
-    timing.begin("IR conversion phase 2");
-    timing.begin("Build post method processor");
-    PostMethodProcessor postMethodProcessor =
-        postMethodProcessorBuilder.build(appView, executorService, timing);
-    timing.end();
-    if (postMethodProcessor != null) {
-      assert !options.debug;
-      assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
-      timing.begin("Process code");
-      postMethodProcessor.forEachMethod(
-          (method, methodProcessingContext) ->
-              processDesugaredMethod(
-                  method, feedback, postMethodProcessor, methodProcessingContext),
-          feedback,
-          executorService,
-          timing);
-      timing.end();
-      timing.begin("Update visible optimization info");
-      feedback.updateVisibleOptimizationInfo();
-      timing.end();
-      assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
-    }
-    timing.end();
-
-    enumUnboxer.unsetRewriter();
-
-    // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
-    // have now been processed and rewritten, we clear code lens rewriting so that the class
-    // staticizer and phase 3 does not perform again the rewriting.
-    appView.clearCodeRewritings();
-
-    // Commit synthetics before creating a builder (otherwise the builder will not include the
-    // synthetics.)
-    commitPendingSyntheticItemsR8(appView);
-
-    // Build a new application with jumbo string info.
-    Builder<?> builder = appView.appInfo().app().builder();
-    builder.setHighestSortingString(highestSortingString);
-
-    if (serviceLoaderRewriter != null) {
-      processSimpleSynthesizeMethods(
-          serviceLoaderRewriter.getServiceLoadMethods(), executorService);
-    }
-
-    if (instanceInitializerOutliner != null) {
-      processSimpleSynthesizeMethods(
-          instanceInitializerOutliner.getSynthesizedMethods(), executorService);
-    }
-    if (assertionErrorTwoArgsConstructorRewriter != null) {
-      processSimpleSynthesizeMethods(
-          assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService);
-    }
-
-    // Update optimization info for all synthesized methods at once.
-    feedback.updateVisibleOptimizationInfo();
-
-    // TODO(b/127694949): Adapt to PostOptimization.
-    outliner.performOutlining(this, feedback, executorService, timing);
-    clearDexMethodCompilationState();
-
-    if (identifierNameStringMarker != null) {
-      identifierNameStringMarker.decoupleIdentifierNameStringsInFields(executorService);
-    }
-
-    if (Log.ENABLED) {
-      if (idempotentFunctionCallCanonicalizer != null) {
-        idempotentFunctionCallCanonicalizer.logResults();
-      }
-      if (libraryMethodOverrideAnalysis != null) {
-        libraryMethodOverrideAnalysis.logResults();
-      }
-      if (stringOptimizer != null) {
-        stringOptimizer.logResult();
-      }
-    }
-
-    // Assure that no more optimization feedback left after post processing.
-    assert feedback.noUpdatesLeft();
-    return builder.build();
-  }
-
-  private void waveStart(ProgramMethodSet wave) {
-    onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
-  }
-
-  private void waveDone(ProgramMethodSet wave, ExecutorService executorService)
-      throws ExecutionException {
-    delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
-    delayedOptimizationFeedback.updateVisibleOptimizationInfo();
-    fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
-    appView.withArgumentPropagator(ArgumentPropagator::publishDelayedReprocessingCriteria);
-    if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
-      appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
-    }
-    enumUnboxer.updateEnumUnboxingCandidatesInfo();
-    assert delayedOptimizationFeedback.noUpdatesLeft();
-    onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
-    onWaveDoneActions = null;
-    if (!prunedMethodsInWave.isEmpty()) {
-      appView.pruneItems(
-          PrunedItems.builder()
-              .setRemovedMethods(prunedMethodsInWave)
-              .setPrunedApp(appView.appInfo().app())
-              .build(),
-          executorService);
-      prunedMethodsInWave.clear();
-    }
-  }
-
-  private void lastWaveDone(
-      PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService)
-      throws ExecutionException {
-    if (inliner != null) {
-      inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
-    }
-
-    // Ensure determinism of method-to-reprocess set.
-    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
-  }
-
   public void addWaveDoneAction(com.android.tools.r8.utils.Action action) {
     if (!appView.enableWholeProgramOptimizations()) {
       throw new Unreachable("addWaveDoneAction() should never be used in D8.");
@@ -908,7 +350,7 @@
     return onWaveDoneActions != null;
   }
 
-  private void processSimpleSynthesizeMethods(
+  protected void processSimpleSynthesizeMethods(
       List<ProgramMethod> serviceLoadMethods, ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(
@@ -922,14 +364,6 @@
     removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
 
-  private void clearDexMethodCompilationState() {
-    appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
-  }
-
-  private void clearDexMethodCompilationState(DexProgramClass clazz) {
-    clazz.forEachMethod(DexEncodedMethod::markNotProcessed);
-  }
-
   /**
    * This will replace the Dex code in the method with the Dex code generated from the provided IR.
    *
@@ -958,18 +392,21 @@
   }
 
   public void optimizeSynthesizedMethods(
-      List<ProgramMethod> programMethods, ExecutorService executorService)
+      List<ProgramMethod> programMethods,
+      MethodProcessorEventConsumer eventConsumer,
+      ExecutorService executorService)
       throws ExecutionException {
     // Process the generated class, but don't apply any outlining.
     ProgramMethodSet methods = ProgramMethodSet.create(programMethods::forEach);
-    processMethodsConcurrently(methods, executorService);
+    processMethodsConcurrently(methods, eventConsumer, executorService);
   }
 
-  public void optimizeSynthesizedMethod(ProgramMethod synthesizedMethod) {
+  public void optimizeSynthesizedMethod(
+      ProgramMethod synthesizedMethod, MethodProcessorEventConsumer eventConsumer) {
     if (!synthesizedMethod.getDefinition().isProcessed()) {
       // Process the generated method, but don't apply any outlining.
       OneTimeMethodProcessor methodProcessor =
-          OneTimeMethodProcessor.create(synthesizedMethod, appView);
+          OneTimeMethodProcessor.create(synthesizedMethod, eventConsumer, appView);
       methodProcessor.forEachWaveWithExtension(
           (method, methodProcessingContext) ->
               processDesugaredMethod(
@@ -978,19 +415,25 @@
   }
 
   public void processClassesConcurrently(
-      Collection<DexProgramClass> classes, ExecutorService executorService)
+      Collection<DexProgramClass> classes,
+      MethodProcessorEventConsumer eventConsumer,
+      ExecutorService executorService)
       throws ExecutionException {
     ProgramMethodSet wave = ProgramMethodSet.create();
     for (DexProgramClass clazz : classes) {
       clazz.forEachProgramMethod(wave::add);
     }
-    processMethodsConcurrently(wave, executorService);
+    processMethodsConcurrently(wave, eventConsumer, executorService);
   }
 
-  public void processMethodsConcurrently(ProgramMethodSet wave, ExecutorService executorService)
+  public void processMethodsConcurrently(
+      ProgramMethodSet wave,
+      MethodProcessorEventConsumer eventConsumer,
+      ExecutorService executorService)
       throws ExecutionException {
     if (!wave.isEmpty()) {
-      OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
+      OneTimeMethodProcessor methodProcessor =
+          OneTimeMethodProcessor.create(wave, eventConsumer, appView);
       methodProcessor.forEachWaveWithExtension(
           (method, methodProcessingContext) ->
               processDesugaredMethod(
@@ -999,10 +442,24 @@
     }
   }
 
-  private String logCode(InternalOptions options, DexEncodedMethod method) {
+  String logCode(InternalOptions options, DexEncodedMethod method) {
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
 
+  void printCfg() throws IOException {
+    if (printer != null) {
+      if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
+        System.out.print(printer.toString());
+      } else {
+        try (OutputStreamWriter writer =
+            new OutputStreamWriter(
+                new FileOutputStream(options.printCfgFile), StandardCharsets.UTF_8)) {
+          writer.write(printer.toString());
+        }
+      }
+    }
+  }
+
   // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
   public Timing processDesugaredMethod(
       ProgramMethod method,
@@ -1029,24 +486,6 @@
     }
   }
 
-  Timing rewriteNonDesugaredCode(
-      ProgramMethod method,
-      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    return ExceptionUtils.withOriginAndPositionAttachmentHandler(
-        method.getOrigin(),
-        new MethodPosition(method.getReference().asMethodReference()),
-        () ->
-            rewriteNonDesugaredCodeInternal(
-                method,
-                desugaringEventConsumer,
-                feedback,
-                methodProcessor,
-                methodProcessingContext));
-  }
-
   Timing rewriteDesugaredCode(
       ProgramMethod method,
       OptimizationFeedback feedback,
@@ -1060,24 +499,7 @@
                 method, feedback, methodProcessor, methodProcessingContext));
   }
 
-  private Timing rewriteNonDesugaredCodeInternal(
-      ProgramMethod method,
-      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    boolean didDesugar = desugar(method, desugaringEventConsumer, methodProcessingContext);
-    if (Log.ENABLED && didDesugar) {
-      Log.debug(
-          getClass(),
-          "Desugared code for %s:\n%s",
-          method.toSourceString(),
-          logCode(options, method.getDefinition()));
-    }
-    return rewriteDesugaredCodeInternal(method, feedback, methodProcessor, methodProcessingContext);
-  }
-
-  private Timing rewriteDesugaredCodeInternal(
+  protected Timing rewriteDesugaredCodeInternal(
       ProgramMethod method,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
@@ -1110,24 +532,8 @@
     return optimize(code, feedback, methodProcessor, methodProcessingContext);
   }
 
-  private boolean desugar(
-      ProgramMethod method,
-      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
-      MethodProcessingContext methodProcessingContext) {
-    // Due to some mandatory desugarings, we need to run desugaring even if desugaring is disabled.
-    if (!method.getDefinition().getCode().isCfCode()) {
-      return false;
-    }
-    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
-  private Timing optimize(
+  Timing optimize(
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
@@ -1253,7 +659,7 @@
 
     if (instanceInitializerOutliner != null) {
       instanceInitializerOutliner.rewriteInstanceInitializers(
-          code, context, methodProcessingContext);
+          code, context, methodProcessor, methodProcessingContext);
       assert code.verifyTypes(appView);
     }
 
@@ -1755,7 +1161,7 @@
     return true;
   }
 
-  private synchronized void updateHighestSortingStrings(DexEncodedMethod method) {
+  protected synchronized void updateHighestSortingStrings(DexEncodedMethod method) {
     Code code = method.getCode();
     assert code.isDexWritableCode();
     DexString highestSortingReferencedString = code.asDexWritableCode().getHighestSortingString();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java
index 74d0376..0f3a0aa 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java
@@ -22,6 +22,11 @@
   public CfCode finalizeCode(
       IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) {
     ProgramMethod method = code.context();
-    return new CfBuilder(appView, method, code, bytecodeMetadataProvider).build(deadCodeRemover);
+    timing.begin("Finalize CF code");
+    CfCode build =
+        new CfBuilder(appView, method, code, bytecodeMetadataProvider)
+            .build(deadCodeRemover, timing);
+    timing.end();
+    return build;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index dbe72e8..ae28180 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -23,6 +23,8 @@
 
   public abstract MethodProcessingContext createMethodProcessingContext(ProgramMethod method);
 
+  public abstract MethodProcessorEventConsumer getEventConsumer();
+
   public abstract boolean isProcessedConcurrently(ProgramMethod method);
 
   public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorEventConsumer.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorEventConsumer.java
new file mode 100644
index 0000000..d002cd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorEventConsumer.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizationsEventConsumer;
+import com.android.tools.r8.ir.optimize.api.InstanceInitializerOutlinerEventConsumer;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxerMethodProcessorEventConsumer;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingMethodProcessorEventConsumer;
+
+public abstract class MethodProcessorEventConsumer
+    implements EnumUnboxerMethodProcessorEventConsumer,
+        InstanceInitializerOutlinerEventConsumer,
+        UtilityMethodsForCodeOptimizationsEventConsumer {
+
+  public static MethodProcessorEventConsumer create(
+      ArtProfileCollectionAdditions artProfileCollectionAdditions) {
+    return ArtProfileRewritingMethodProcessorEventConsumer.attach(
+        artProfileCollectionAdditions, empty());
+  }
+
+  public static MethodProcessorEventConsumer empty() {
+    return EmptyMethodProcessorEventConsumer.getInstance();
+  }
+
+  private static class EmptyMethodProcessorEventConsumer extends MethodProcessorEventConsumer {
+
+    private static final EmptyMethodProcessorEventConsumer INSTANCE =
+        new EmptyMethodProcessorEventConsumer();
+
+    private EmptyMethodProcessorEventConsumer() {}
+
+    static EmptyMethodProcessorEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptEnumUnboxerCheckNotZeroContext(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptEnumUnboxerLocalUtilityClassMethodContext(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptEnumUnboxerSharedUtilityClassMethodContext(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptInstanceInitializerOutline(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptUtilityThrowIllegalAccessErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptUtilityThrowNoSuchMethodErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index 65a3287..c243d46 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -18,34 +18,49 @@
  */
 public class OneTimeMethodProcessor extends MethodProcessorWithWave {
 
+  private final MethodProcessorEventConsumer eventConsumer;
   private final ProcessorContext processorContext;
 
-  private OneTimeMethodProcessor(ProcessorContext processorContext, ProgramMethodSet wave) {
+  private OneTimeMethodProcessor(
+      MethodProcessorEventConsumer eventConsumer,
+      ProcessorContext processorContext,
+      ProgramMethodSet wave) {
+    this.eventConsumer = eventConsumer;
     this.processorContext = processorContext;
     this.wave = wave;
   }
 
-  public static Builder builder(ProcessorContext processorContext) {
-    return new Builder(processorContext);
-  }
-
-  public static OneTimeMethodProcessor create(ProgramMethod methodToProcess, AppView<?> appView) {
-    return create(ProgramMethodSet.create(methodToProcess), appView);
+  public static Builder builder(
+      MethodProcessorEventConsumer eventConsumer, ProcessorContext processorContext) {
+    return new Builder(eventConsumer, processorContext);
   }
 
   public static OneTimeMethodProcessor create(
-      ProgramMethod methodToProcess, ProcessorContext processorContext) {
-    return create(ProgramMethodSet.create(methodToProcess), processorContext);
+      ProgramMethod methodToProcess,
+      MethodProcessorEventConsumer eventConsumer,
+      AppView<?> appView) {
+    return create(ProgramMethodSet.create(methodToProcess), eventConsumer, appView);
   }
 
   public static OneTimeMethodProcessor create(
-      ProgramMethodSet methodsToProcess, AppView<?> appView) {
-    return create(methodsToProcess, appView.createProcessorContext());
+      ProgramMethod methodToProcess,
+      MethodProcessorEventConsumer eventConsumer,
+      ProcessorContext processorContext) {
+    return create(ProgramMethodSet.create(methodToProcess), eventConsumer, processorContext);
   }
 
   public static OneTimeMethodProcessor create(
-      ProgramMethodSet methodsToProcess, ProcessorContext processorContext) {
-    return new OneTimeMethodProcessor(processorContext, methodsToProcess);
+      ProgramMethodSet methodsToProcess,
+      MethodProcessorEventConsumer eventConsumer,
+      AppView<?> appView) {
+    return create(methodsToProcess, eventConsumer, appView.createProcessorContext());
+  }
+
+  public static OneTimeMethodProcessor create(
+      ProgramMethodSet methodsToProcess,
+      MethodProcessorEventConsumer eventConsumer,
+      ProcessorContext processorContext) {
+    return new OneTimeMethodProcessor(eventConsumer, processorContext, methodsToProcess);
   }
 
   @Override
@@ -54,6 +69,11 @@
   }
 
   @Override
+  public MethodProcessorEventConsumer getEventConsumer() {
+    return eventConsumer;
+  }
+
+  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     return true;
   }
@@ -86,9 +106,12 @@
   public static class Builder {
 
     private final ProgramMethodSet methodsToProcess = ProgramMethodSet.create();
+
+    private final MethodProcessorEventConsumer eventConsumer;
     private final ProcessorContext processorContext;
 
-    Builder(ProcessorContext processorContext) {
+    Builder(MethodProcessorEventConsumer eventConsumer, ProcessorContext processorContext) {
+      this.eventConsumer = eventConsumer;
       this.processorContext = processorContext;
     }
 
@@ -98,7 +121,7 @@
     }
 
     public OneTimeMethodProcessor build() {
-      return create(methodsToProcess, processorContext);
+      return create(methodsToProcess, eventConsumer, processorContext);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index dd82c2d..c7b0ae3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -36,11 +36,16 @@
 
 public class PostMethodProcessor extends MethodProcessorWithWave {
 
+  private final MethodProcessorEventConsumer eventConsumer;
   private final ProcessorContext processorContext;
   private final Deque<ProgramMethodSet> waves;
   private final ProgramMethodSet processed = ProgramMethodSet.create();
 
-  private PostMethodProcessor(AppView<AppInfoWithLiveness> appView, CallGraph callGraph) {
+  private PostMethodProcessor(
+      AppView<AppInfoWithLiveness> appView,
+      CallGraph callGraph,
+      MethodProcessorEventConsumer eventConsumer) {
+    this.eventConsumer = eventConsumer;
     this.processorContext = appView.createProcessorContext();
     this.waves = createWaves(callGraph);
   }
@@ -51,6 +56,11 @@
   }
 
   @Override
+  public MethodProcessorEventConsumer getEventConsumer() {
+    return eventConsumer;
+  }
+
+  @Override
   public boolean isPostMethodProcessor() {
     return true;
   }
@@ -120,7 +130,10 @@
     }
 
     PostMethodProcessor build(
-        AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+        AppView<AppInfoWithLiveness> appView,
+        MethodProcessorEventConsumer eventConsumer,
+        ExecutorService executorService,
+        Timing timing)
         throws ExecutionException {
       Set<DexMethod> reprocessMethods = appView.appInfo().getReprocessMethods();
       if (!reprocessMethods.isEmpty()) {
@@ -142,7 +155,7 @@
       ProgramMethodSet methodsToReprocess = methodsToReprocessBuilder.build(appView);
       CallGraph callGraph =
           new PartialCallGraphBuilder(appView, methodsToReprocess).build(executorService, timing);
-      return new PostMethodProcessor(appView, callGraph);
+      return new PostMethodProcessor(appView, callGraph, eventConsumer);
     }
 
     public void dump(DeterminismChecker determinismChecker) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
new file mode 100644
index 0000000..1d06a25
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -0,0 +1,417 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
+import static com.android.tools.r8.ir.desugar.lambda.D8LambdaDesugaring.rewriteEnclosingLambdaMethodAttributes;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.ProgramAdditions;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
+import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
+import com.android.tools.r8.ir.desugar.itf.L8InnerOuterAttributeEraser;
+import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
+import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class PrimaryD8L8IRConverter extends IRConverter {
+
+  public PrimaryD8L8IRConverter(AppView<AppInfo> appView, Timing timing) {
+    super(appView, timing, appView.options().printCfg ? new CfgPrinter() : null);
+  }
+
+  public void convert(AppView<AppInfo> appView, ExecutorService executorService)
+      throws ExecutionException, IOException {
+    LambdaDeserializationMethodRemover.run(appView);
+    workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService);
+    DexApplication application = appView.appInfo().app();
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.create(appView);
+    D8MethodProcessor methodProcessor =
+        new D8MethodProcessor(artProfileCollectionAdditions, this, executorService);
+    InterfaceProcessor interfaceProcessor = InterfaceProcessor.create(appView);
+
+    timing.begin("IR conversion");
+
+    convertClasses(methodProcessor, interfaceProcessor, executorService);
+
+    reportNestDesugarDependencies();
+    clearNestAttributes();
+
+    if (instanceInitializerOutliner != null) {
+      processSimpleSynthesizeMethods(
+          instanceInitializerOutliner.getSynthesizedMethods(), executorService);
+    }
+    if (assertionErrorTwoArgsConstructorRewriter != null) {
+      processSimpleSynthesizeMethods(
+          assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService);
+    }
+
+    application = commitPendingSyntheticItems(appView, application);
+
+    postProcessingDesugaringForD8(methodProcessor, interfaceProcessor, executorService);
+
+    application = commitPendingSyntheticItems(appView, application);
+
+    // Build a new application with jumbo string info,
+    Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
+
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
+      new L8InnerOuterAttributeEraser(appView).run();
+    }
+
+    processCovariantReturnTypeAnnotations(builder);
+
+    timing.end();
+
+    application = builder.build();
+    appView.setAppInfo(
+        new AppInfo(
+            appView.appInfo().getSyntheticItems().commit(application),
+            appView.appInfo().getMainDexInfo()));
+
+    artProfileCollectionAdditions.commit(appView);
+
+    printCfg();
+  }
+
+  void convertMethods(
+      DexProgramClass clazz,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      D8MethodProcessor methodProcessor,
+      InterfaceProcessor interfaceProcessor) {
+    // When converting all methods on a class always convert <clinit> first.
+    ProgramMethod classInitializer = clazz.getProgramClassInitializer();
+
+    // TODO(b/179755192): We currently need to copy the class' methods, to avoid a
+    //  ConcurrentModificationException from the insertion of methods due to invoke-special
+    //  desugaring. By building up waves of methods in the class converter, we would not need to
+    //  iterate the methods of a class during while its methods are being processed, which avoids
+    //  the need to copy the method list.
+    List<ProgramMethod> methods = ListUtils.newArrayList(clazz::forEachProgramMethod);
+    if (classInitializer != null) {
+      methodProcessor.processMethod(classInitializer, desugaringEventConsumer);
+    }
+
+    for (ProgramMethod method : methods) {
+      if (!method.getDefinition().isClassInitializer()) {
+        methodProcessor.processMethod(method, desugaringEventConsumer);
+        if (interfaceProcessor != null) {
+          interfaceProcessor.processMethod(method, desugaringEventConsumer);
+        }
+      }
+    }
+
+    // The class file version is downgraded after compilation. Some of the desugaring might need
+    // the initial class file version to determine how far a method can be downgraded.
+    if (options.isGeneratingClassFiles() && clazz.hasClassFileVersion()) {
+      clazz.downgradeInitialClassFileVersion(
+          appView.options().classFileVersionAfterDesugaring(clazz.getInitialClassFileVersion()));
+    }
+  }
+
+  void convertMethod(
+      ProgramMethod method,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (options.isGeneratingClassFiles() && definition.hasClassFileVersion()) {
+      definition.downgradeClassFileVersion(
+          appView.options().classFileVersionAfterDesugaring(definition.getClassFileVersion()));
+    }
+    if (definition.getCode() == null) {
+      return;
+    }
+    if (!options.methodMatchesFilter(definition)) {
+      return;
+    }
+    checkPrefixMerging(method);
+    if (options.isGeneratingClassFiles()
+        || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
+      // We do not process in call graph order, so anything could be a leaf.
+      rewriteNonDesugaredCode(
+          method,
+          desugaringEventConsumer,
+          simpleOptimizationFeedback,
+          methodProcessor,
+          methodProcessingContext);
+    } else {
+      assert definition.getCode().isDexCode();
+    }
+    if (!options.isGeneratingClassFiles()) {
+      updateHighestSortingStrings(definition);
+    }
+  }
+
+  private void checkPrefixMerging(ProgramMethod method) {
+    if (!appView.options().enableNeverMergePrefixes) {
+      return;
+    }
+    DexString descriptor = method.getHolderType().descriptor;
+    for (DexString neverMergePrefix : neverMerge.getPrefixes()) {
+      if (descriptor.startsWith(neverMergePrefix)) {
+        seenNeverMergePrefix.getAndSet(true);
+      } else {
+        for (DexString exceptionPrefix : neverMerge.getExceptionPrefixes()) {
+          if (!descriptor.startsWith(exceptionPrefix)) {
+            seenNotNeverMergePrefix.getAndSet(true);
+            break;
+          }
+        }
+      }
+      // Don't mix.
+      // TODO(b/168001352): Consider requiring that no 'never merge' prefix is ever seen as a
+      //  passthrough object.
+      if (seenNeverMergePrefix.get() && seenNotNeverMergePrefix.get()) {
+        StringBuilder message = new StringBuilder();
+        message
+            .append("Merging DEX file containing classes with prefix")
+            .append(neverMerge.getPrefixes().size() > 1 ? "es " : " ");
+        for (int i = 0; i < neverMerge.getPrefixes().size(); i++) {
+          message
+              .append("'")
+              .append(neverMerge.getPrefixes().get(i).toString().substring(1).replace('/', '.'))
+              .append("'")
+              .append(i < neverMerge.getPrefixes().size() - 1 ? ", " : "");
+        }
+        if (!neverMerge.getExceptionPrefixes().isEmpty()) {
+          message
+              .append(" with other classes, except classes with prefix")
+              .append(neverMerge.getExceptionPrefixes().size() > 1 ? "es " : " ");
+          for (int i = 0; i < neverMerge.getExceptionPrefixes().size(); i++) {
+            message
+                .append("'")
+                .append(
+                    neverMerge
+                        .getExceptionPrefixes()
+                        .get(i)
+                        .toString()
+                        .substring(1)
+                        .replace('/', '.'))
+                .append("'")
+                .append(i < neverMerge.getExceptionPrefixes().size() - 1 ? ", " : "");
+          }
+          message.append(",");
+        } else {
+          message.append(" with classes with any other prefixes");
+        }
+        message.append(" is not allowed: ");
+        boolean first = true;
+        int limit = 11;
+        for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+          if (!clazz.type.descriptor.startsWith(neverMergePrefix)) {
+            boolean hasExceptionPrefix = false;
+            for (DexString exceptionPrefix : neverMerge.getExceptionPrefixes()) {
+              hasExceptionPrefix =
+                  hasExceptionPrefix | clazz.type.descriptor.startsWith(exceptionPrefix);
+            }
+            if (hasExceptionPrefix) {
+              continue;
+            }
+            if (limit-- < 0) {
+              message.append("..");
+              break;
+            }
+            if (first) {
+              first = false;
+            } else {
+              message.append(", ");
+            }
+            message.append(clazz.type);
+          }
+        }
+        message.append(".");
+        throw new CompilationError(message.toString());
+      }
+    }
+  }
+
+  void classSynthesisDesugaring(
+      ExecutorService executorService,
+      CfClassSynthesizerDesugaringEventConsumer classSynthesizerEventConsumer)
+      throws ExecutionException {
+    CfClassSynthesizerDesugaringCollection.create(appView)
+        .synthesizeClasses(executorService, classSynthesizerEventConsumer);
+  }
+
+  private DexApplication commitPendingSyntheticItems(
+      AppView<AppInfo> appView, DexApplication application) {
+    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+      appView.setAppInfo(
+          new AppInfo(
+              appView.appInfo().getSyntheticItems().commit(application),
+              appView.appInfo().getMainDexInfo()));
+      application = appView.appInfo().app();
+    }
+    return application;
+  }
+
+  private void convertClasses(
+      D8MethodProcessor methodProcessor,
+      InterfaceProcessor interfaceProcessor,
+      ExecutorService executorService)
+      throws ExecutionException {
+    ClassConverterResult classConverterResult =
+        ClassConverter.create(appView, this, methodProcessor, interfaceProcessor)
+            .convertClasses(executorService);
+
+    // The synthesis of accessibility bridges in nest based access desugaring will schedule and
+    // await the processing of synthesized methods.
+    synthesizeBridgesForNestBasedAccessesOnClasspath(methodProcessor, executorService);
+
+    // There should be no outstanding method processing.
+    methodProcessor.verifyNoPendingMethodProcessing();
+
+    rewriteEnclosingLambdaMethodAttributes(
+        appView, classConverterResult.getForcefullyMovedLambdaMethods());
+
+    instructionDesugaring.withDesugaredLibraryAPIConverter(
+        DesugaredLibraryAPIConverter::generateTrackingWarnings);
+  }
+
+  private void postProcessingDesugaringForD8(
+      D8MethodProcessor methodProcessor,
+      InterfaceProcessor interfaceProcessor,
+      ExecutorService executorService)
+      throws ExecutionException {
+    CfPostProcessingDesugaringEventConsumer eventConsumer =
+        CfPostProcessingDesugaringEventConsumer.createForD8(
+            methodProcessor.getArtProfileCollectionAdditions(),
+            methodProcessor,
+            instructionDesugaring);
+    methodProcessor.newWave();
+    InterfaceMethodProcessorFacade interfaceDesugaring =
+        instructionDesugaring.getInterfaceMethodPostProcessingDesugaringD8(
+            ExcludeDexResources, interfaceProcessor);
+    CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring, m -> true)
+        .postProcessingDesugaring(appView.appInfo().classes(), eventConsumer, executorService);
+    methodProcessor.awaitMethodProcessing();
+    eventConsumer.finalizeDesugaring();
+  }
+
+  void prepareDesugaring(
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer, ExecutorService executorService)
+      throws ExecutionException {
+    // Prepare desugaring by collecting all the synthetic methods required on program classes.
+    ProgramAdditions programAdditions = new ProgramAdditions();
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          clazz.forEachProgramMethodMatching(
+              method -> method.hasCode() && method.getCode().isCfCode(),
+              method ->
+                  instructionDesugaring.prepare(method, desugaringEventConsumer, programAdditions));
+        },
+        executorService);
+    programAdditions.apply(executorService);
+  }
+
+  private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
+    if (covariantReturnTypeAnnotationTransformer != null) {
+      covariantReturnTypeAnnotationTransformer.process(builder);
+    }
+  }
+
+  Timing rewriteNonDesugaredCode(
+      ProgramMethod method,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    return ExceptionUtils.withOriginAndPositionAttachmentHandler(
+        method.getOrigin(),
+        new MethodPosition(method.getReference().asMethodReference()),
+        () ->
+            rewriteNonDesugaredCodeInternal(
+                method,
+                desugaringEventConsumer,
+                feedback,
+                methodProcessor,
+                methodProcessingContext));
+  }
+
+  private Timing rewriteNonDesugaredCodeInternal(
+      ProgramMethod method,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    boolean didDesugar = desugar(method, desugaringEventConsumer, methodProcessingContext);
+    if (Log.ENABLED && didDesugar) {
+      Log.debug(
+          getClass(),
+          "Desugared code for %s:\n%s",
+          method.toSourceString(),
+          logCode(options, method.getDefinition()));
+    }
+    return rewriteDesugaredCodeInternal(method, feedback, methodProcessor, methodProcessingContext);
+  }
+
+  private boolean desugar(
+      ProgramMethod method,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    // Due to some mandatory desugarings, we need to run desugaring even if desugaring is disabled.
+    if (!method.getDefinition().getCode().isCfCode()) {
+      return false;
+    }
+    instructionDesugaring.scan(method, desugaringEventConsumer);
+    if (instructionDesugaring.needsDesugaring(method)) {
+      instructionDesugaring.desugar(method, methodProcessingContext, desugaringEventConsumer);
+      return true;
+    }
+    return false;
+  }
+
+  private void clearNestAttributes() {
+    instructionDesugaring.withD8NestBasedAccessDesugaring(
+        D8NestBasedAccessDesugaring::clearNestAttributes);
+  }
+
+  private void reportNestDesugarDependencies() {
+    instructionDesugaring.withD8NestBasedAccessDesugaring(
+        D8NestBasedAccessDesugaring::reportDesugarDependencies);
+  }
+
+  private void synthesizeBridgesForNestBasedAccessesOnClasspath(
+      D8MethodProcessor methodProcessor, ExecutorService executorService)
+      throws ExecutionException {
+    instructionDesugaring.withD8NestBasedAccessDesugaring(
+        d8NestBasedAccessDesugaring ->
+            d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
+                methodProcessor, executorService));
+    methodProcessor.awaitMethodProcessing();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index c1ce6d2..805067a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -43,23 +43,29 @@
 
   private final AppView<?> appView;
   private final CallSiteInformation callSiteInformation;
+  private final MethodProcessorEventConsumer eventConsumer;
   private final Deque<ProgramMethodSet> waves;
 
   private ProcessorContext processorContext;
 
-  private PrimaryMethodProcessor(AppView<AppInfoWithLiveness> appView, CallGraph callGraph) {
+  private PrimaryMethodProcessor(
+      AppView<AppInfoWithLiveness> appView,
+      CallGraph callGraph,
+      MethodProcessorEventConsumer eventConsumer) {
     this.appView = appView;
     this.callSiteInformation = callGraph.createCallSiteInformation(appView);
+    this.eventConsumer = eventConsumer;
     this.waves = createWaves(appView, callGraph);
   }
 
   static PrimaryMethodProcessor create(
       AppView<AppInfoWithLiveness> appView,
+      MethodProcessorEventConsumer eventConsumer,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
     CallGraph callGraph = CallGraph.builder(appView).build(executorService, timing);
-    return new PrimaryMethodProcessor(appView, callGraph);
+    return new PrimaryMethodProcessor(appView, callGraph, eventConsumer);
   }
 
   @Override
@@ -68,6 +74,11 @@
   }
 
   @Override
+  public MethodProcessorEventConsumer getEventConsumer() {
+    return eventConsumer;
+  }
+
+  @Override
   public boolean isPrimaryMethodProcessor() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
new file mode 100644
index 0000000..aae02ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -0,0 +1,301 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class PrimaryR8IRConverter extends IRConverter {
+
+  public PrimaryR8IRConverter(AppView<? extends AppInfoWithClassHierarchy> appView, Timing timing) {
+    super(appView, timing, appView.options().printCfg ? new CfgPrinter() : null);
+  }
+
+  public void optimize(AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
+      throws ExecutionException, IOException {
+    timing.begin("Create IR");
+    try {
+      DexApplication application =
+          internalOptimize(appView.withLiveness(), executorService).asDirect();
+      AppInfoWithClassHierarchy newAppInfo =
+          appView.appInfo().rebuildWithClassHierarchy(previous -> application);
+      appView.withClassHierarchy().setAppInfo(newAppInfo);
+    } finally {
+      timing.end();
+    }
+    printCfg();
+  }
+
+  private DexApplication internalOptimize(
+      AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
+      throws ExecutionException {
+    // Desugaring happens in the enqueuer.
+    assert instructionDesugaring.isEmpty();
+
+    workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService);
+
+    // The process is in two phases in general.
+    // 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
+    //    reprocessing IR code of methods, e.g., outlining, double-inlining, class staticizer, etc.
+    //    - a side effect is candidates for those optimizations are identified.
+    // 2) Revisit DexEncodedMethods for the collected candidates.
+
+    printPhase("Primary optimization pass");
+
+    GraphLens graphLensForPrimaryOptimizationPass = appView.graphLens();
+
+    // Setup optimizations for the primary optimization pass.
+    appView.withArgumentPropagator(
+        argumentPropagator -> argumentPropagator.initializeCodeScanner(executorService, timing));
+    enumUnboxer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
+    outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
+
+    if (fieldAccessAnalysis != null) {
+      fieldAccessAnalysis.fieldAssignmentTracker().initialize();
+    }
+
+    // Process the application identifying outlining candidates.
+    OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
+    PostMethodProcessor.Builder postMethodProcessorBuilder =
+        new PostMethodProcessor.Builder(graphLensForPrimaryOptimizationPass);
+    {
+      timing.begin("Build primary method processor");
+      ArtProfileCollectionAdditions artProfileCollectionAdditions =
+          ArtProfileCollectionAdditions.create(appView);
+      MethodProcessorEventConsumer eventConsumer =
+          MethodProcessorEventConsumer.create(artProfileCollectionAdditions);
+      PrimaryMethodProcessor primaryMethodProcessor =
+          PrimaryMethodProcessor.create(
+              appView.withLiveness(), eventConsumer, executorService, timing);
+      timing.end();
+      timing.begin("IR conversion phase 1");
+      assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
+      primaryMethodProcessor.forEachMethod(
+          (method, methodProcessingContext) ->
+              processDesugaredMethod(
+                  method, feedback, primaryMethodProcessor, methodProcessingContext),
+          this::waveStart,
+          this::waveDone,
+          timing,
+          executorService);
+      lastWaveDone(postMethodProcessorBuilder, executorService);
+      timing.time("Commit profile additions", () -> artProfileCollectionAdditions.commit(appView));
+      assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
+      timing.end();
+    }
+
+    // The field access info collection is not maintained during IR processing.
+    appView.appInfo().withLiveness().getFieldAccessInfoCollection().destroyAccessContexts();
+
+    // Assure that no more optimization feedback left after primary processing.
+    assert feedback.noUpdatesLeft();
+    appView.setAllCodeProcessed();
+
+    // All the code has been processed so the rewriting required by the lenses is done everywhere,
+    // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
+    // lenses with code rewriting are added.
+    appView.clearCodeRewritings();
+
+    // Commit synthetics from the primary optimization pass.
+    commitPendingSyntheticItems(appView);
+
+    // Post processing:
+    //   1) Second pass for methods whose collected call site information become more precise.
+    //   2) Second inlining pass for dealing with double inline callers.
+    printPhase("Post optimization pass");
+
+    // Analyze the data collected by the argument propagator, use the analysis result to update
+    // the parameter optimization infos, and rewrite the application.
+    // TODO(b/199237357): Automatically rewrite state when lens changes.
+    enumUnboxer.rewriteWithLens();
+    outliner.rewriteWithLens();
+    appView.withArgumentPropagator(
+        argumentPropagator ->
+            argumentPropagator.tearDownCodeScanner(
+                this, postMethodProcessorBuilder, executorService, timing));
+
+    if (libraryMethodOverrideAnalysis != null) {
+      libraryMethodOverrideAnalysis.finish();
+    }
+
+    if (!options.debug) {
+      new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
+          .run(executorService, feedback, timing);
+    }
+
+    outliner.rewriteWithLens();
+    enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback);
+    appView.unboxedEnums().checkEnumsUnboxed(appView);
+
+    GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
+
+    outliner.rewriteWithLens();
+
+    {
+      timing.begin("IR conversion phase 2");
+      ArtProfileCollectionAdditions artProfileCollectionAdditions =
+          ArtProfileCollectionAdditions.create(appView);
+      PostMethodProcessor postMethodProcessor =
+          timing.time(
+              "Build post method processor",
+              () -> {
+                MethodProcessorEventConsumer eventConsumer =
+                    MethodProcessorEventConsumer.create(artProfileCollectionAdditions);
+                return postMethodProcessorBuilder.build(
+                    appView, eventConsumer, executorService, timing);
+              });
+      if (postMethodProcessor != null) {
+        assert !options.debug;
+        assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
+        timing.begin("Process code");
+        postMethodProcessor.forEachMethod(
+            (method, methodProcessingContext) ->
+                processDesugaredMethod(
+                    method, feedback, postMethodProcessor, methodProcessingContext),
+            feedback,
+            executorService,
+            timing);
+        timing.end();
+        timing.time("Update visible optimization info", feedback::updateVisibleOptimizationInfo);
+        timing.time(
+            "Commit profile additions", () -> artProfileCollectionAdditions.commit(appView));
+        assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
+      }
+      timing.end();
+    }
+
+    enumUnboxer.unsetRewriter();
+
+    // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
+    // have now been processed and rewritten, we clear code lens rewriting so that the class
+    // staticizer and phase 3 does not perform again the rewriting.
+    appView.clearCodeRewritings();
+
+    // Commit synthetics before creating a builder (otherwise the builder will not include the
+    // synthetics.)
+    commitPendingSyntheticItems(appView);
+
+    // Build a new application with jumbo string info.
+    Builder<?> builder = appView.appInfo().app().builder();
+    builder.setHighestSortingString(highestSortingString);
+
+    if (serviceLoaderRewriter != null) {
+      processSimpleSynthesizeMethods(
+          serviceLoaderRewriter.getServiceLoadMethods(), executorService);
+    }
+
+    if (instanceInitializerOutliner != null) {
+      processSimpleSynthesizeMethods(
+          instanceInitializerOutliner.getSynthesizedMethods(), executorService);
+    }
+    if (assertionErrorTwoArgsConstructorRewriter != null) {
+      processSimpleSynthesizeMethods(
+          assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService);
+    }
+
+    // Update optimization info for all synthesized methods at once.
+    feedback.updateVisibleOptimizationInfo();
+
+    // TODO(b/127694949): Adapt to PostOptimization.
+    outliner.performOutlining(this, feedback, executorService, timing);
+    clearDexMethodCompilationState();
+
+    if (identifierNameStringMarker != null) {
+      identifierNameStringMarker.decoupleIdentifierNameStringsInFields(executorService);
+    }
+
+    if (Log.ENABLED) {
+      if (idempotentFunctionCallCanonicalizer != null) {
+        idempotentFunctionCallCanonicalizer.logResults();
+      }
+      if (libraryMethodOverrideAnalysis != null) {
+        libraryMethodOverrideAnalysis.logResults();
+      }
+      if (stringOptimizer != null) {
+        stringOptimizer.logResult();
+      }
+    }
+
+    // Assure that no more optimization feedback left after post processing.
+    assert feedback.noUpdatesLeft();
+    return builder.build();
+  }
+
+  private void clearDexMethodCompilationState() {
+    appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
+  }
+
+  private void clearDexMethodCompilationState(DexProgramClass clazz) {
+    clazz.forEachMethod(DexEncodedMethod::markNotProcessed);
+  }
+
+  private static void commitPendingSyntheticItems(AppView<AppInfoWithLiveness> appView) {
+    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+      appView.setAppInfo(
+          appView
+              .appInfo()
+              .rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
+    }
+  }
+
+  private void waveStart(ProgramMethodSet wave) {
+    onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
+  }
+
+  private void waveDone(ProgramMethodSet wave, ExecutorService executorService)
+      throws ExecutionException {
+    delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
+    delayedOptimizationFeedback.updateVisibleOptimizationInfo();
+    fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
+    appView.withArgumentPropagator(ArgumentPropagator::publishDelayedReprocessingCriteria);
+    if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
+      appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
+    }
+    enumUnboxer.updateEnumUnboxingCandidatesInfo();
+    assert delayedOptimizationFeedback.noUpdatesLeft();
+    onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
+    onWaveDoneActions = null;
+    if (!prunedMethodsInWave.isEmpty()) {
+      appView.pruneItems(
+          PrunedItems.builder()
+              .setRemovedMethods(prunedMethodsInWave)
+              .setPrunedApp(appView.appInfo().app())
+              .build(),
+          executorService);
+      prunedMethodsInWave.clear();
+    }
+  }
+
+  private void lastWaveDone(
+      PostMethodProcessor.Builder postMethodProcessorBuilder, ExecutorService executorService)
+      throws ExecutionException {
+    if (inliner != null) {
+      inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
+    }
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
index dfaa50d..f2cdd81 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
@@ -9,56 +9,81 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
-import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
-public class CfClassSynthesizerDesugaringEventConsumer
+public abstract class CfClassSynthesizerDesugaringEventConsumer
     implements L8ProgramEmulatedInterfaceSynthesizerEventConsumer,
         DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer,
         DesugaredLibraryRetargeterL8SynthesizerEventConsumer,
-        RecordDesugaringEventConsumer,
+        RecordClassSynthesizerDesugaringEventConsumer,
         VarHandleDesugaringEventConsumer {
 
-  private Set<DexProgramClass> synthesizedClasses = Sets.newConcurrentHashSet();
+  protected CfClassSynthesizerDesugaringEventConsumer() {}
 
-  @Override
-  public void acceptProgramEmulatedInterface(DexProgramClass clazz) {
-    synthesizedClasses.add(clazz);
+  public static CfClassSynthesizerDesugaringEventConsumer create(
+      ArtProfileCollectionAdditions artProfileCollectionAdditions) {
+    CfClassSynthesizerDesugaringEventConsumer eventConsumer =
+        new D8R8CfClassSynthesizerDesugaringEventConsumer();
+    return ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.attach(
+        artProfileCollectionAdditions, eventConsumer);
   }
 
-  @Override
-  public void acceptWrapperProgramClass(DexProgramClass clazz) {
-    synthesizedClasses.add(clazz);
-  }
+  public abstract Set<DexProgramClass> getSynthesizedClasses();
 
-  @Override
-  public void acceptEnumConversionProgramClass(DexProgramClass clazz) {
-    synthesizedClasses.add(clazz);
-  }
+  private static class D8R8CfClassSynthesizerDesugaringEventConsumer
+      extends CfClassSynthesizerDesugaringEventConsumer {
 
-  @Override
-  public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
-    synthesizedClasses.add(clazz);
-  }
+    private final Set<DexProgramClass> synthesizedClasses = Sets.newConcurrentHashSet();
 
-  @Override
-  public void acceptRecordClass(DexProgramClass clazz) {
-    synthesizedClasses.add(clazz);
-  }
+    @Override
+    public void acceptProgramEmulatedInterface(DexProgramClass clazz) {
+      synthesizedClasses.add(clazz);
+    }
 
-  @Override
-  public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
-    synthesizedClasses.add(clazz);
-  }
+    @Override
+    public void acceptWrapperProgramClass(DexProgramClass clazz) {
+      synthesizedClasses.add(clazz);
+    }
 
-  public Set<DexProgramClass> getSynthesizedClasses() {
-    return synthesizedClasses;
-  }
+    @Override
+    public void acceptEnumConversionProgramClass(DexProgramClass clazz) {
+      synthesizedClasses.add(clazz);
+    }
 
-  @Override
-  public void acceptCollectionConversion(ProgramMethod arrayConversion) {
-    synthesizedClasses.add(arrayConversion.getHolder());
+    @Override
+    public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+      synthesizedClasses.add(clazz);
+    }
+
+    @Override
+    public void acceptRecordClass(DexProgramClass recordTagClass) {
+      synthesizedClasses.add(recordTagClass);
+    }
+
+    @Override
+    public void acceptRecordClassContext(
+        DexProgramClass recordTagClass, DexProgramClass recordClass) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+      synthesizedClasses.add(clazz);
+    }
+
+    @Override
+    public Set<DexProgramClass> getSynthesizedClasses() {
+      return synthesizedClasses;
+    }
+
+    @Override
+    public void acceptCollectionConversion(ProgramMethod arrayConversion) {
+      synthesizedClasses.add(arrayConversion.getHolder());
+    }
   }
 }
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 d852270..595d1db 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
@@ -75,7 +75,7 @@
         new D8CfInstructionDesugaringEventConsumer(
             appView, classConverterResultBuilder, methodProcessor);
     return ArtProfileRewritingCfInstructionDesugaringEventConsumer.attach(
-        artProfileCollectionAdditions, eventConsumer);
+        appView, artProfileCollectionAdditions, eventConsumer);
   }
 
   public static CfInstructionDesugaringEventConsumer createForR8(
@@ -95,7 +95,7 @@
             additions,
             companionMethodConsumer);
     return ArtProfileRewritingCfInstructionDesugaringEventConsumer.attach(
-        artProfileCollectionAdditions, eventConsumer);
+        appView, artProfileCollectionAdditions, eventConsumer);
   }
 
   public abstract List<ProgramMethod> finalizeDesugaring();
@@ -173,7 +173,23 @@
     }
 
     @Override
-    public void acceptRecordMethod(ProgramMethod method) {
+    public void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty. Added to the program using ProgramAdditions.
+    }
+
+    @Override
+    public void acceptRecordGetFieldsAsObjectsHelperMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty. Added to the program using ProgramAdditions.
+    }
+
+    @Override
+    public void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
+    public void acceptRecordToStringHelperMethod(ProgramMethod method, ProgramMethod context) {
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
@@ -191,6 +207,11 @@
     }
 
     @Override
+    public void acceptRecordClassContext(DexProgramClass recordTagClass, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
       clazz
           .programMethods()
@@ -245,7 +266,41 @@
     }
 
     @Override
-    public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+    public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowIllegalAccessErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowNoSuchMethodErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    private void acceptUtilityMethod(ProgramMethod method, ProgramMethod context) {
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
@@ -423,6 +478,11 @@
     }
 
     @Override
+    public void acceptRecordClassContext(DexProgramClass recordTagClass, ProgramMethod context) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
       // Intentionally empty. The class will be hit by tracing if required.
     }
@@ -433,7 +493,23 @@
     }
 
     @Override
-    public void acceptRecordMethod(ProgramMethod method) {
+    public void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
+
+    @Override
+    public void acceptRecordGetFieldsAsObjectsHelperMethod(
+        ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
+
+    @Override
+    public void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
+
+    @Override
+    public void acceptRecordToStringHelperMethod(ProgramMethod method, ProgramMethod context) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
 
@@ -443,7 +519,41 @@
     }
 
     @Override
-    public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+    public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowIllegalAccessErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowNoSuchMethodErrorMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    @Override
+    public void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+        ProgramMethod method, ProgramMethod context) {
+      acceptUtilityMethod(method, context);
+    }
+
+    private void acceptUtilityMethod(ProgramMethod method, ProgramMethod context) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index 8770cd1..e3f1f89 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -21,10 +21,12 @@
 public abstract class CfPostProcessingDesugaringCollection {
 
   public static CfPostProcessingDesugaringCollection create(
-      AppView<?> appView, InterfaceMethodProcessorFacade interfaceMethodProcessorFacade) {
+      AppView<?> appView,
+      InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+      Predicate<ProgramMethod> isLiveMethod) {
     if (appView.options().desugarState.isOn()) {
       return NonEmptyCfPostProcessingDesugaringCollection.create(
-          appView, interfaceMethodProcessorFacade);
+          appView, interfaceMethodProcessorFacade, isLiveMethod);
     }
     return empty();
   }
@@ -35,7 +37,6 @@
 
   public abstract void postProcessingDesugaring(
       Collection<DexProgramClass> programClasses,
-      Predicate<ProgramMethod> isLiveMethod,
       CfPostProcessingDesugaringEventConsumer eventConsumer,
       ExecutorService executorService)
       throws ExecutionException;
@@ -51,7 +52,9 @@
     }
 
     public static CfPostProcessingDesugaringCollection create(
-        AppView<?> appView, InterfaceMethodProcessorFacade interfaceMethodProcessorFacade) {
+        AppView<?> appView,
+        InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
+        Predicate<ProgramMethod> isLiveMethod) {
       ArrayList<CfPostProcessingDesugaring> desugarings = new ArrayList<>();
       if (appView.options().machineDesugaredLibrarySpecification.hasRetargeting()
           && !appView.options().isDesugaredLibraryCompilation()) {
@@ -62,7 +65,7 @@
       }
       DesugaredLibraryAPICallbackSynthesizer apiCallbackSynthesizor =
           appView.typeRewriter.isRewriting()
-              ? new DesugaredLibraryAPICallbackSynthesizer(appView)
+              ? new DesugaredLibraryAPICallbackSynthesizer(appView, isLiveMethod)
               : null;
       // At this point the desugaredLibraryAPIConverter is required to be last to generate
       // call-backs on the forwarding methods.
@@ -87,7 +90,6 @@
     @Override
     public void postProcessingDesugaring(
         Collection<DexProgramClass> programClasses,
-        Predicate<ProgramMethod> isLiveMethod,
         CfPostProcessingDesugaringEventConsumer eventConsumer,
         ExecutorService executorService)
         throws ExecutionException {
@@ -112,7 +114,6 @@
     @Override
     public void postProcessingDesugaring(
         Collection<DexProgramClass> programClasses,
-        Predicate<ProgramMethod> isLiveMethod,
         CfPostProcessingDesugaringEventConsumer eventConsumer,
         ExecutorService executorService)
         throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 23d3e1a..29cb618 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
+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.ProgramMethod;
@@ -18,6 +19,8 @@
 import com.android.tools.r8.profile.art.rewriting.ArtProfileRewritingCfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.Collections;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiConsumer;
 
@@ -52,6 +55,8 @@
         artProfileCollectionAdditions, eventConsumer);
   }
 
+  public abstract Set<DexMethod> getNewlyLiveMethods();
+
   public abstract void finalizeDesugaring() throws ExecutionException;
 
   public static class D8CfPostProcessingDesugaringEventConsumer
@@ -125,6 +130,11 @@
     }
 
     @Override
+    public Set<DexMethod> getNewlyLiveMethods() {
+      return Collections.emptySet();
+    }
+
+    @Override
     public void finalizeDesugaring() throws ExecutionException {
       assert methodProcessor.verifyNoPendingMethodProcessing();
       methodProcessor.newWave();
@@ -176,6 +186,13 @@
     }
 
     @Override
+    public Set<DexMethod> getNewlyLiveMethods() {
+      // Note: this answers the newly live methods up until the point where this is called.
+      // This has to be called in between post processing to be deterministic.
+      return additions.getNewlyLiveMethods();
+    }
+
+    @Override
     public void finalizeDesugaring() {
       // Intentionally empty.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 5d774b2..1143afd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.google.common.base.Predicates;
 import java.util.HashSet;
@@ -51,7 +52,9 @@
 // several CovariantReturnType annotations. In this case, a new method is synthesized for each of
 // the contained CovariantReturnType annotations.
 public final class CovariantReturnTypeAnnotationTransformer {
+
   private final IRConverter converter;
+  private final MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
   private final DexItemFactory factory;
 
   public CovariantReturnTypeAnnotationTransformer(IRConverter converter, DexItemFactory factory) {
@@ -180,7 +183,7 @@
             .build();
     // Optimize to generate DexCode instead of CfCode.
     ProgramMethod programMethod = new ProgramMethod(methodHolder, newVirtualMethod);
-    converter.optimizeSynthesizedMethod(programMethod);
+    converter.optimizeSynthesizedMethod(programMethod, eventConsumer);
     return newVirtualMethod;
   }
 
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 9d6e3df..f9d4355 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
@@ -576,7 +576,11 @@
       return callTarget;
     }
 
-    boolean isInterface() {
+    public Type getInvokeType() {
+      return invokeType;
+    }
+
+    public boolean isInterface() {
       return isInterface;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java b/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java
index 24cd2b5..1b07110 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/UnrepresentableInDexInstructionRemover.java
@@ -87,12 +87,11 @@
     void invokeThrowingStub(
         MethodProcessingContext methodProcessingContext,
         CfInstructionDesugaringEventConsumer eventConsumer,
-        ProgramMethod context,
         ImmutableList.Builder<CfInstruction> builder) {
       UtilityMethodForCodeOptimizations throwUtility =
-          synthesizeThrowRuntimeExceptionWithMessageMethod(appView, methodProcessingContext);
+          synthesizeThrowRuntimeExceptionWithMessageMethod(
+              appView, eventConsumer, methodProcessingContext);
       ProgramMethod throwMethod = throwUtility.uncheckedGetMethod();
-      eventConsumer.acceptThrowMethod(throwMethod, context);
       builder.add(
           createMessageString(),
           new CfInvoke(Opcodes.INVOKESTATIC, throwMethod.getReference(), false),
@@ -173,7 +172,7 @@
                 DexCallSite callSite = invokeDynamic.getCallSite();
                 pop(callSite.getMethodProto(), replacement);
                 localStackAllocator.allocateLocalStack(1);
-                invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+                invokeThrowingStub(methodProcessingContext, eventConsumer, replacement);
                 pushReturnValue(callSite.getMethodProto().getReturnType(), replacement);
                 return replacement.build();
               })
@@ -224,7 +223,7 @@
                   pop(dexItemFactory.objectType, replacement);
                 }
                 localStackAllocator.allocateLocalStack(1);
-                invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+                invokeThrowingStub(methodProcessingContext, eventConsumer, replacement);
                 pushReturnValue(invoke.getMethod().getReturnType(), replacement);
                 return replacement.build();
               })
@@ -264,7 +263,7 @@
                   dexItemFactory) -> {
                 report(context);
                 Builder<CfInstruction> replacement = ImmutableList.builder();
-                invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+                invokeThrowingStub(methodProcessingContext, eventConsumer, replacement);
                 return replacement.add(new CfConstNull()).build();
               })
           .build();
@@ -303,7 +302,7 @@
                   dexItemFactory) -> {
                 report(context);
                 Builder<CfInstruction> replacement = ImmutableList.builder();
-                invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+                invokeThrowingStub(methodProcessingContext, eventConsumer, replacement);
                 return replacement.add(new CfConstNull()).build();
               })
           .build();
@@ -343,7 +342,7 @@
                   dexItemFactory) -> {
                 report(context);
                 Builder<CfInstruction> replacement = ImmutableList.builder();
-                invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
+                invokeThrowingStub(methodProcessingContext, eventConsumer, replacement);
                 return pushReturnValue(constDynamic.getType(), replacement).build();
               })
           .build();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index a4b79c0..5105707 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -76,8 +76,6 @@
   public static final String CONST_FIELD_NAME = "CONST";
 
   private final AppView<?> appView;
-  private final ConstantDynamicInstructionDesugaring desugaring;
-  private final ProgramMethod context;
   public final ConstantDynamicReference reference;
   public final DexField initializedValueField;
   public final DexField constantValueField;
@@ -93,13 +91,10 @@
   public ConstantDynamicClass(
       SyntheticProgramClassBuilder builder,
       AppView<?> appView,
-      ConstantDynamicInstructionDesugaring desugaring,
       ProgramMethod context,
       CfConstDynamic constantDynamic) {
     DexItemFactory factory = appView.dexItemFactory();
     this.appView = appView;
-    this.desugaring = desugaring;
-    this.context = context;
     this.reference = constantDynamic.getReference();
     this.constantValueField =
         factory.createField(
@@ -190,19 +185,16 @@
             ? UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod
             : UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod,
         eventConsumer,
-        context,
         methodProcessingContext);
   }
 
   private Collection<CfInstruction> desugarToThrow(
       MethodSynthesizerConsumer methodSynthesizerConsumer,
       ConstantDynamicDesugaringEventConsumer eventConsumer,
-      ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
     UtilityMethodForCodeOptimizations throwMethod =
-        methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
+        methodSynthesizerConsumer.synthesizeMethod(appView, eventConsumer, methodProcessingContext);
     ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
-    eventConsumer.acceptThrowMethod(throwProgramMethod, context);
     return ImmutableList.of(new CfInvoke(INVOKESTATIC, throwProgramMethod.getReference(), false));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
index 1d2dcf6..052b8b9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
@@ -4,10 +4,10 @@
 package com.android.tools.r8.ir.desugar.constantdynamic;
 
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizationsEventConsumer;
 
-public interface ConstantDynamicDesugaringEventConsumer {
+public interface ConstantDynamicDesugaringEventConsumer
+    extends UtilityMethodsForCodeOptimizationsEventConsumer {
 
   void acceptConstantDynamicClass(ConstantDynamicClass lambdaClass, ProgramMethod context);
-
-  void acceptThrowMethod(ProgramMethod method, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
index b9826d5..67f3e0b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -203,9 +203,7 @@
                 methodProcessingContext.createUniqueContext(),
                 appView,
                 builder ->
-                    box.set(
-                        new ConstantDynamicClass(
-                            builder, appView, this, context, constantDynamic)));
+                    box.set(new ConstantDynamicClass(builder, appView, context, constantDynamic)));
     // Immediately set the actual program class on the constant dynamic.
     ConstantDynamicClass constantDynamicClass = box.get();
     constantDynamicClass.setClass(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index c1e11d4..8f6ad62 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -26,6 +26,7 @@
 import java.util.Collection;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Predicate;
 
 public class DesugaredLibraryAPICallbackSynthesizer implements CfPostProcessingDesugaring {
 
@@ -35,9 +36,13 @@
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
   private final Set<DexMethod> trackedCallBackAPIs;
 
-  public DesugaredLibraryAPICallbackSynthesizer(AppView<?> appView) {
+  private final Predicate<ProgramMethod> isLiveMethod;
+
+  public DesugaredLibraryAPICallbackSynthesizer(
+      AppView<?> appView, Predicate<ProgramMethod> isLiveMethod) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
+    this.isLiveMethod = isLiveMethod;
     this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView);
     if (appView.options().testing.trackDesugaredAPIConversions) {
       trackedCallBackAPIs = Sets.newConcurrentHashSet();
@@ -54,6 +59,7 @@
       ExecutorService executorService) {
     ProcessorContext processorContext = appView.createProcessorContext();
     MainThreadContext mainThreadContext = processorContext.createMainThreadContext();
+    Set<DexMethod> newlyLiveMethods = eventConsumer.getNewlyLiveMethods();
     assert noPendingWrappersOrConversions();
     for (DexProgramClass clazz : programClasses) {
       if (!appView.isAlreadyLibraryDesugared(clazz)) {
@@ -62,6 +68,10 @@
         // always be live in R8.
         for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
           if (shouldRegisterCallback(virtualProgramMethod)) {
+            if (!isLiveMethod(virtualProgramMethod, newlyLiveMethods)) {
+              // This happens for live non instantiated types, library overrides are not live there.
+              continue;
+            }
             if (trackedCallBackAPIs != null) {
               trackedCallBackAPIs.add(virtualProgramMethod.getReference());
             }
@@ -82,6 +92,12 @@
     generateTrackingWarnings();
   }
 
+  private boolean isLiveMethod(
+      ProgramMethod virtualProgramMethod, Set<DexMethod> newlyLiveMethods) {
+    return isLiveMethod.test(virtualProgramMethod)
+        || newlyLiveMethods.contains(virtualProgramMethod.getReference());
+  }
+
   private boolean noPendingWrappersOrConversions() {
     for (DexProgramClass pendingSyntheticClass :
         appView.getSyntheticItems().getPendingSyntheticClasses()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
new file mode 100644
index 0000000..c76c17f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2023, 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.desugaredlibrary.lint;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+
+public abstract class AbstractGenerateFiles {
+
+  private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
+
+  // If we increment this api level, we need to verify everything works correctly.
+  static final AndroidApiLevel MAX_TESTED_ANDROID_API_LEVEL = AndroidApiLevel.T;
+
+  private final DexItemFactory factory = new DexItemFactory();
+  private final Reporter reporter = new Reporter();
+  final InternalOptions options = new InternalOptions(factory, reporter);
+
+  final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
+  final Path desugaredLibrarySpecificationPath;
+  final Collection<Path> desugaredLibraryImplementation;
+  final Path outputDirectory;
+
+  public AbstractGenerateFiles(
+      String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
+      throws Exception {
+    this(
+        Paths.get(desugarConfigurationPath),
+        ImmutableList.of(Paths.get(desugarImplementationPath)),
+        Paths.get(outputDirectory));
+  }
+
+  AbstractGenerateFiles(
+      Path desugarConfigurationPath,
+      Collection<Path> desugarImplementationPath,
+      Path outputDirectory)
+      throws Exception {
+    this.desugaredLibrarySpecificationPath = desugarConfigurationPath;
+    DesugaredLibrarySpecification specification =
+        readDesugaredLibraryConfiguration(desugarConfigurationPath);
+    Path androidJarPath = getAndroidJarPath(specification.getRequiredCompilationApiLevel());
+    DexApplication app = createApp(androidJarPath, options);
+    this.desugaredLibrarySpecification = specification.toMachineSpecification(app, Timing.empty());
+    this.desugaredLibraryImplementation = desugarImplementationPath;
+    this.outputDirectory = outputDirectory;
+    if (!Files.isDirectory(this.outputDirectory)) {
+      throw new Exception("Output directory " + outputDirectory + " is not a directory");
+    }
+  }
+
+  static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
+    String jar =
+        apiLevel == AndroidApiLevel.MASTER
+            ? "third_party/android_jar/lib-master/android.jar"
+            : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+    return Paths.get(jar);
+  }
+
+
+  private DesugaredLibrarySpecification readDesugaredLibraryConfiguration(
+      Path desugarConfigurationPath) {
+    return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
+        StringResource.fromFile(desugarConfigurationPath),
+        factory,
+        reporter,
+        false,
+        AndroidApiLevel.B.getLevel());
+  }
+
+  private static DexApplication createApp(Path androidLib, InternalOptions options)
+      throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
+    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    assert !options.ignoreJavaLibraryOverride;
+    options.ignoreJavaLibraryOverride = true;
+    DexApplication app = applicationReader.read(executorService);
+    options.ignoreJavaLibraryOverride = false;
+    return app;
+  }
+
+  abstract AndroidApiLevel run() throws Exception;
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 3) {
+      new GenerateLintFiles(args[0], args[1], args[2]).run();
+      return;
+    }
+    if (args.length == 4 && args[0].equals("--generate-api-docs")) {
+      new GenerateHtmlDoc(args[1], args[2], args[3]).run();
+      return;
+    }
+    throw new RuntimeException(
+        StringUtils.joinLines(
+            "Invalid invocation.",
+            "Usage: GenerateLineFiles [--generate-api-docs] "
+                + "<desugar configuration> <desugar implementation> <output directory>"));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
new file mode 100644
index 0000000..859bc95
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -0,0 +1,548 @@
+// Copyright (c) 2023, 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.desugaredlibrary.lint;
+
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.SupportedClass;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.StreamSupport;
+
+public class GenerateHtmlDoc extends AbstractGenerateFiles {
+
+  private static final String HTML_SPLIT = "<br>&nbsp;";
+  private static final int MAX_LINE_CHARACTERS = 53;
+  private static final String SUP_1 = "<sup>1</sup>";
+  private static final String SUP_2 = "<sup>2</sup>";
+  private static final String SUP_3 = "<sup>3</sup>";
+  private static final String SUP_4 = "<sup>4</sup>";
+
+  public GenerateHtmlDoc(
+      String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
+      throws Exception {
+    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory);
+  }
+
+  private static class StringBuilderWithIndent {
+
+    String NL = System.lineSeparator();
+    StringBuilder builder = new StringBuilder();
+    String indent = "";
+
+    StringBuilderWithIndent() {}
+
+    StringBuilderWithIndent indent(String indent) {
+      this.indent = indent;
+      return this;
+    }
+
+    StringBuilderWithIndent appendLineStart(String lineStart) {
+      builder.append(indent);
+      builder.append(lineStart);
+      return this;
+    }
+
+    StringBuilderWithIndent append(String string) {
+      builder.append(string);
+      return this;
+    }
+
+    StringBuilderWithIndent appendLineEnd(String lineEnd) {
+      builder.append(lineEnd);
+      builder.append(NL);
+      return this;
+    }
+
+    StringBuilderWithIndent appendLine(String line) {
+      builder.append(indent);
+      builder.append(line);
+      builder.append(NL);
+      return this;
+    }
+
+    StringBuilderWithIndent emptyLine() {
+      builder.append(NL);
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return builder.toString();
+    }
+  }
+
+  private abstract static class SourceBuilder<B extends GenerateHtmlDoc.SourceBuilder> {
+
+    protected final DexClass clazz;
+    protected List<DexEncodedField> fields = new ArrayList<>();
+    protected Map<DexEncodedMethod, MethodAnnotation> constructors =
+        new TreeMap<>(Comparator.comparing(DexEncodedMethod::getReference));
+    protected Map<DexEncodedMethod, MethodAnnotation> methods =
+        new TreeMap<>(Comparator.comparing(DexEncodedMethod::getReference));
+
+    String className;
+    String packageName;
+
+    private SourceBuilder(DexClass clazz) {
+      this.clazz = clazz;
+      this.className = clazz.type.toSourceString();
+      int index = this.className.lastIndexOf('.');
+      this.packageName = index > 0 ? this.className.substring(0, index) : "";
+    }
+
+    public abstract B self();
+
+    private B addField(DexEncodedField field) {
+      fields.add(field);
+      return self();
+    }
+
+    private B addMethod(DexEncodedMethod method, MethodAnnotation methodAnnotation) {
+      assert !method.isClassInitializer();
+      if (method.isInitializer()) {
+        constructors.put(method, methodAnnotation);
+      } else {
+        methods.put(method, methodAnnotation);
+      }
+      return self();
+    }
+
+    // If we are in a.b.c, then anything starting with a.b should not be fully qualified.
+    protected String typeInPackageRecursive(String typeName, String packageName) {
+      String rewritten = typeInPackage(typeName, packageName);
+      if (rewritten != null) {
+        return rewritten;
+      }
+      String[] split = packageName.split("\\.");
+      if (split.length > 2) {
+        String prevPackage =
+            packageName.substring(0, packageName.length() - split[split.length - 1].length() - 1);
+        return typeInPackage(typeName, prevPackage);
+      }
+      return null;
+    }
+
+    protected String typeInPackage(String typeName, String packageName) {
+      if (typeName.startsWith(packageName)
+          && typeName.length() > packageName.length()
+          && typeName.charAt(packageName.length()) == '.') {
+        int last = typeName.lastIndexOf('.') + 1;
+        return typeName.substring(last);
+      }
+      return null;
+    }
+
+    protected String typeInPackage(String typeName) {
+      String result = typeInPackageRecursive(typeName, packageName);
+      if (result == null) {
+        result = typeInPackage(typeName, "java.lang");
+      }
+      if (result == null) {
+        result = typeInPackage(typeName, "java.util.function");
+      }
+      if (result == null) {
+        result = typeName;
+      }
+      return result.replace('$', '.');
+    }
+
+    protected String typeInPackage(DexType type) {
+      if (type.isPrimitiveType()) {
+        return type.toSourceString();
+      }
+      return typeInPackage(type.toSourceString());
+    }
+
+    protected String accessFlags(ClassAccessFlags accessFlags) {
+      List<String> flags = new ArrayList<>();
+      if (accessFlags.isPublic()) {
+        flags.add("public");
+      }
+      if (accessFlags.isProtected()) {
+        flags.add("protected");
+      }
+      if (accessFlags.isPrivate()) {
+        assert false;
+        flags.add("private");
+      }
+      if (accessFlags.isPackagePrivate()) {
+        assert false;
+        flags.add("/* package */");
+      }
+      if (accessFlags.isAbstract() && !accessFlags.isInterface()) {
+        flags.add("abstract");
+      }
+      if (accessFlags.isStatic()) {
+        flags.add("static");
+      }
+      if (accessFlags.isFinal()) {
+        flags.add("final");
+      }
+      return String.join(" ", flags);
+    }
+
+    protected String accessFlags(FieldAccessFlags accessFlags) {
+      List<String> flags = new ArrayList<>();
+      if (accessFlags.isPublic()) {
+        flags.add("public");
+      }
+      if (accessFlags.isProtected()) {
+        flags.add("protected");
+      }
+      if (accessFlags.isPrivate()) {
+        assert false;
+        flags.add("private");
+      }
+      if (accessFlags.isPackagePrivate()) {
+        assert false;
+        flags.add("/* package */");
+      }
+      if (accessFlags.isStatic()) {
+        flags.add("static");
+      }
+      if (accessFlags.isFinal()) {
+        flags.add("final");
+      }
+      return String.join(" ", flags);
+    }
+
+    protected String accessFlags(MethodAccessFlags accessFlags) {
+      List<String> flags = new ArrayList<>();
+      if (accessFlags.isPublic()) {
+        flags.add("public");
+      }
+      if (accessFlags.isProtected()) {
+        flags.add("protected");
+      }
+      if (accessFlags.isPrivate()) {
+        assert false;
+        flags.add("private");
+      }
+      if (accessFlags.isPackagePrivate()) {
+        assert false;
+        flags.add("/* package */");
+      }
+      if (accessFlags.isAbstract()) {
+        flags.add("abstract");
+      }
+      if (accessFlags.isStatic()) {
+        flags.add("static");
+      }
+      if (accessFlags.isFinal()) {
+        flags.add("final");
+      }
+      return String.join(" ", flags);
+    }
+
+    public String arguments(DexEncodedMethod method) {
+      DexProto proto = method.getReference().proto;
+      StringBuilder argsBuilder = new StringBuilder();
+      boolean firstArg = true;
+      int argIndex = method.isVirtualMethod() || method.accessFlags.isConstructor() ? 1 : 0;
+      int argNumber = 0;
+      argsBuilder.append("(");
+      for (DexType type : proto.parameters.values) {
+        if (!firstArg) {
+          argsBuilder.append(", ");
+        }
+        if (method.hasCode()) {
+          String name = "p" + argNumber;
+          for (LocalVariableInfo localVariable : method.getCode().asCfCode().getLocalVariables()) {
+            if (localVariable.getIndex() == argIndex) {
+              assert !localVariable.getLocal().name.toString().equals("this");
+              name = localVariable.getLocal().name.toString();
+            }
+          }
+          argsBuilder.append(typeInPackage(type)).append(" ").append(name);
+        } else {
+          argsBuilder.append(typeInPackage(type)).append(" p").append(argNumber);
+        }
+        firstArg = false;
+        argIndex += type.isWideType() ? 2 : 1;
+        argNumber++;
+      }
+      argsBuilder.append(")");
+      return argsBuilder.toString();
+    }
+  }
+
+  private static class HTMLBuilder extends StringBuilderWithIndent {
+
+    private String indent = "";
+
+    private void increaseIndent() {
+      indent += "  ";
+      indent(indent);
+    }
+
+    private void decreaseIndent() {
+      indent = indent.substring(0, indent.length() - 2);
+      indent(indent);
+    }
+
+    HTMLBuilder appendTdPackage(String s) {
+      String finalString = format(s, 4);
+      appendLineStart("<td><code><em>" + finalString + "</em></code><br>");
+      if (s.startsWith("java.time")) {
+        append("<a href=\"#java-time-customizations\">See customizations</a><br");
+      } else if (s.startsWith("java.nio")) {
+        append("<a href=\"#java-nio-customizations\">See customizations</a><br");
+      }
+      return this;
+    }
+
+    private String format(String s, int i) {
+      String[] regexpSplit = s.split("\\.");
+      if (regexpSplit.length < i) {
+        return s;
+      }
+      int splitIndex = 0;
+      int mid = i / 2;
+      for (int j = 0; j < mid; j++) {
+        splitIndex += regexpSplit[j].length();
+      }
+      splitIndex += mid;
+      return s.substring(0, splitIndex) + HTML_SPLIT + s.substring(splitIndex);
+    }
+
+    HTMLBuilder appendTdClassName(String s) {
+      String finalString = format(s, 2);
+      appendLineEnd(
+          "<code><br><br><div style=\"font-size:small;font-weight:bold;\">&nbsp;"
+              + finalString
+              + "</div></code><br><br></td>");
+      return this;
+    }
+
+    HTMLBuilder appendTdP(String s) {
+      appendLine("<td><p>" + s + "</p></td>");
+      return this;
+    }
+
+    HTMLBuilder appendLiCode(String s) {
+      appendLine("<li class=\"java8_table\"><code>" + s + "</code></li>");
+      return this;
+    }
+
+    HTMLBuilder appendMethodLiCode(String s) {
+      if (s.length() < MAX_LINE_CHARACTERS || s.contains("()")) {
+        return appendLiCode(s);
+      }
+      StringBuilder sb = new StringBuilder();
+      String[] split = s.split("\\(");
+      sb.append(split[0]).append('(').append(HTML_SPLIT);
+      if (split[1].length() < MAX_LINE_CHARACTERS - 2) {
+        sb.append(split[1]);
+        return appendLiCode(sb.toString());
+      }
+      String[] secondSplit = split[1].split(",");
+      sb.append("&nbsp;");
+      for (int i = 0; i < secondSplit.length; i++) {
+        sb.append(secondSplit[i]);
+        if (i != secondSplit.length - 1) {
+          sb.append(',');
+          sb.append(HTML_SPLIT);
+        }
+      }
+      return appendLiCode(sb.toString());
+    }
+
+    HTMLBuilder start(String tag) {
+      appendLine("<" + tag + ">");
+      increaseIndent();
+      return this;
+    }
+
+    HTMLBuilder end(String tag) {
+      decreaseIndent();
+      appendLine("</" + tag + ">");
+      return this;
+    }
+  }
+
+  public static class HTMLSourceBuilder extends SourceBuilder<HTMLSourceBuilder> {
+
+    private final ClassAnnotation classAnnotation;
+    private boolean parallelStreamMethod = false;
+    private boolean missingFromLatestAndroidJar = false;
+    private boolean unsupportedInMinApiRange = false;
+    private boolean covariantReturnSupported = false;
+
+    public HTMLSourceBuilder(DexClass clazz, ClassAnnotation classAnnotation) {
+      super(clazz);
+      this.classAnnotation = classAnnotation;
+    }
+
+    @Override
+    public HTMLSourceBuilder self() {
+      return this;
+    }
+
+    private String getTextAnnotations(MethodAnnotation annotation) {
+      if (annotation == null) {
+        return "";
+      }
+      StringBuilder stringBuilder = new StringBuilder();
+      if (annotation.parallelStreamMethod) {
+        stringBuilder.append(SUP_1);
+        parallelStreamMethod = true;
+      }
+      if (annotation.missingFromLatestAndroidJar) {
+        stringBuilder.append(SUP_2);
+        missingFromLatestAndroidJar = true;
+      }
+      if (annotation.unsupportedInMinApiRange) {
+        stringBuilder.append(SUP_3);
+        unsupportedInMinApiRange = true;
+      }
+      if (annotation.covariantReturnSupported) {
+        stringBuilder.append(SUP_4);
+        covariantReturnSupported = true;
+      }
+      return stringBuilder.toString();
+    }
+
+    @Override
+    public String toString() {
+      HTMLBuilder builder = new HTMLBuilder();
+      builder.start("tr");
+      if (packageName.length() > 0) {
+        builder.appendTdPackage(packageName);
+      }
+      builder.appendTdClassName(typeInPackage(className));
+      builder
+          .start("td")
+          .start(
+              "ul style=\"list-style-position:inside; list-style-type: none !important;"
+                  + " margin-left:0px;padding-left:0px !important;\"");
+      if (!fields.isEmpty()) {
+        for (DexEncodedField field : fields) {
+          builder.appendLiCode(
+              accessFlags(field.accessFlags)
+                  + " "
+                  + typeInPackage(field.getReference().type)
+                  + " "
+                  + field.getReference().name);
+        }
+      }
+      if (!constructors.isEmpty()) {
+        for (DexEncodedMethod constructor : constructors.keySet()) {
+          builder.appendMethodLiCode(
+              accessFlags(constructor.accessFlags)
+                  + " "
+                  + typeInPackage(className)
+                  + arguments(constructor)
+                  + getTextAnnotations(constructors.get(constructor)));
+        }
+      }
+      if (!methods.isEmpty()) {
+        for (DexEncodedMethod method : methods.keySet()) {
+          builder.appendMethodLiCode(
+              accessFlags(method.accessFlags)
+                  + " "
+                  + typeInPackage(method.getReference().proto.returnType)
+                  + " "
+                  + method.getReference().name
+                  + arguments(method)
+                  + getTextAnnotations(methods.get(method)));
+        }
+      }
+      builder.end("ul").end("td");
+      StringBuilder commentBuilder = new StringBuilder();
+      if (classAnnotation.isFullySupported()) {
+        commentBuilder.append("Fully implemented class.").append(HTML_SPLIT);
+      }
+      if (parallelStreamMethod) {
+        commentBuilder
+            .append(SUP_1)
+            .append("Supported only on devices which API level is 21 or higher.")
+            .append(HTML_SPLIT);
+      }
+      if (missingFromLatestAndroidJar) {
+        commentBuilder
+            .append(SUP_2)
+            .append("Not present in Android ")
+            .append(MAX_TESTED_ANDROID_API_LEVEL)
+            .append(" (May not resolve at compilation).")
+            .append(HTML_SPLIT);
+      }
+      if (unsupportedInMinApiRange) {
+        commentBuilder
+            .append(SUP_3)
+            .append(" Not supported at all minSDK levels.")
+            .append(HTML_SPLIT);
+      }
+      if (covariantReturnSupported) {
+        commentBuilder
+            .append(SUP_4)
+            .append(" Also supported with covariant return type.")
+            .append(HTML_SPLIT);
+      }
+      if (!classAnnotation.getUnsupportedMethods().isEmpty()) {
+        commentBuilder
+            .append("Some methods (")
+            .append(classAnnotation.getUnsupportedMethods().size())
+            .append(") present in Android ")
+            .append(MAX_TESTED_ANDROID_API_LEVEL)
+            .append(" are not supported.");
+      }
+      builder.appendTdP(commentBuilder.toString());
+      builder.end("tr");
+      return builder.toString();
+    }
+  }
+
+  private void generateClassHTML(PrintStream ps, SupportedClass supportedClass) {
+    DexClass clazz = supportedClass.getClazz();
+    SourceBuilder<HTMLSourceBuilder> builder =
+        new HTMLSourceBuilder(clazz, supportedClass.getClassAnnotation());
+    // We need to extend to support fields.
+    StreamSupport.stream(clazz.fields().spliterator(), false)
+        .filter(field -> field.accessFlags.isPublic() || field.accessFlags.isProtected())
+        .sorted(Comparator.comparing(DexEncodedField::toSourceString))
+        .forEach(builder::addField);
+    supportedClass.forEachMethodAndAnnotation(
+        (method, methodAnnotation) -> {
+          if ((method.accessFlags.isPublic() || method.accessFlags.isProtected())
+              && !method.accessFlags.isBridge()) {
+            builder.addMethod(method, methodAnnotation);
+          }
+        });
+    ps.println(builder);
+  }
+
+  @Override
+  AndroidApiLevel run() throws Exception {
+    PrintStream ps = new PrintStream(Files.newOutputStream(outputDirectory.resolve("apis.html")));
+
+    SupportedClasses supportedClasses =
+        new SupportedMethodsGenerator(options)
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+
+    // Full classes added.
+    supportedClasses.forEachClass(supportedClass -> generateClassHTML(ps, supportedClass));
+    return MAX_TESTED_ANDROID_API_LEVEL;
+  }
+
+  public static void main(String[] args) throws Exception {
+    AbstractGenerateFiles.main(args);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index 422be3d..b7a1a27 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -5,58 +5,39 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
 
 import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.StringResource;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 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.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
 import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -65,29 +46,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import java.util.stream.StreamSupport;
 
-public class GenerateLintFiles {
-
-  private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
-
-  private final DexItemFactory factory = new DexItemFactory();
-  private final Reporter reporter = new Reporter();
-  private final InternalOptions options =
-      new InternalOptions(factory, reporter).withModifications(options -> options.tool = Tool.L8);
-
-  private final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
-  private final Collection<Path> desugaredLibraryImplementation;
-  private final Path outputDirectory;
-
-  private final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
+public class GenerateLintFiles extends AbstractGenerateFiles {
 
   public static GenerateLintFiles createForTesting(
       Path specification, Set<Path> implementation, Path outputDirectory) throws Exception {
@@ -97,69 +59,15 @@
   public GenerateLintFiles(
       String desugarConfigurationPath, String desugarImplementationPath, String outputDirectory)
       throws Exception {
-    this(
-        Paths.get(desugarConfigurationPath),
-        ImmutableList.of(Paths.get(desugarImplementationPath)),
-        Paths.get(outputDirectory));
+    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory);
   }
 
-  private GenerateLintFiles(
+  public GenerateLintFiles(
       Path desugarConfigurationPath,
       Collection<Path> desugarImplementationPath,
       Path outputDirectory)
       throws Exception {
-    DesugaredLibrarySpecification specification =
-        readDesugaredLibraryConfiguration(desugarConfigurationPath);
-    Path androidJarPath = getAndroidJarPath(specification.getRequiredCompilationApiLevel());
-    DexApplication app = createApp(androidJarPath, options);
-    this.desugaredLibrarySpecification = specification.toMachineSpecification(app, Timing.empty());
-
-    this.desugaredLibraryImplementation = desugarImplementationPath;
-    this.outputDirectory = outputDirectory;
-    if (!Files.isDirectory(this.outputDirectory)) {
-      throw new Exception("Output directory " + outputDirectory + " is not a directory");
-    }
-
-    DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;"));
-    DexMethod parallelMethod =
-        factory.createMethod(
-            factory.collectionType,
-            factory.createProto(streamType),
-            factory.createString("parallelStream"));
-    parallelMethods.add(parallelMethod);
-    DexType baseStreamType =
-        factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
-    for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
-      streamType =
-          factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
-      parallelMethod =
-          factory.createMethod(
-              streamType, factory.createProto(streamType), factory.createString("parallel"));
-      parallelMethods.add(parallelMethod);
-      // Also filter out the generated bridges for the covariant return type.
-      parallelMethod =
-          factory.createMethod(
-              streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
-      parallelMethods.add(parallelMethod);
-    }
-  }
-
-  private static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
-    String jar =
-        apiLevel == AndroidApiLevel.MASTER
-            ? "third_party/android_jar/lib-master/android.jar"
-            : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
-    return Paths.get(jar);
-  }
-
-  private DesugaredLibrarySpecification readDesugaredLibraryConfiguration(
-      Path desugarConfigurationPath) {
-    return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-        StringResource.fromFile(desugarConfigurationPath),
-        factory,
-        reporter,
-        false,
-        AndroidApiLevel.B.getLevel());
+    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory);
   }
 
   private CfCode buildEmptyThrowingCfCode(DexMethod method) {
@@ -227,101 +135,6 @@
     builder.addProgramClass(programClass);
   }
 
-  public static class SupportedMethods {
-
-    public final Set<DexClass> classesWithAllMethodsSupported;
-    public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
-
-    public SupportedMethods(
-        Set<DexClass> classesWithAllMethodsSupported,
-        Map<DexClass, List<DexEncodedMethod>> supportedMethods) {
-      this.classesWithAllMethodsSupported = classesWithAllMethodsSupported;
-      this.supportedMethods = supportedMethods;
-    }
-  }
-
-  private SupportedMethods collectSupportedMethods(
-      AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported) throws Exception {
-
-    // Read the android.jar for the compilation API level. Read it as program instead of library
-    // to get the local information for parameter names.
-    AndroidApp library =
-        AndroidApp.builder().addProgramFiles(getAndroidJarPath(compilationApiLevel)).build();
-    DirectMappedDexApplication dexApplication =
-        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
-
-    AndroidApp implementation =
-        AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
-    DirectMappedDexApplication implementationApplication =
-        new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
-
-    options.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
-    List<DexMethod> backports =
-        BackportedMethodRewriter.generateListOfBackportedMethods(
-            implementation, options, ThreadUtils.getExecutorService(1));
-
-    // Collect all the methods that the library desugar configuration adds support for.
-    Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
-    Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
-    for (DexProgramClass clazz : dexApplication.classes()) {
-      if (clazz.accessFlags.isPublic() && desugaredLibrarySpecification.isSupported(clazz.type)) {
-        DexProgramClass implementationClass =
-            implementationApplication.programDefinitionFor(clazz.getType());
-        if (implementationClass == null) {
-          throw new Exception("Implementation class not found for " + clazz.toSourceString());
-        }
-        boolean allMethodsAdded = true;
-        for (DexEncodedMethod method : clazz.methods()) {
-          if (!method.isPublic()) {
-            continue;
-          }
-          ProgramMethod implementationMethod =
-              implementationClass.lookupProgramMethod(method.getReference());
-          // Don't include methods which are not implemented by the desugared library.
-          if (supported.test(method)
-              && (implementationMethod != null || backports.contains(method.getReference()))) {
-            supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
-          } else {
-            allMethodsAdded = false;
-          }
-        }
-        if (allMethodsAdded) {
-          classesWithAllMethodsSupported.add(clazz);
-        }
-      }
-
-      // All emulated interfaces static and default methods are supported.
-      if (desugaredLibrarySpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
-        assert clazz.isInterface();
-        for (DexEncodedMethod method : clazz.methods()) {
-          if (!method.isDefaultMethod() && !method.isStatic()) {
-            continue;
-          }
-          if (supported.test(method)) {
-            supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
-          }
-        }
-      }
-    }
-
-    // All retargeted methods are supported.
-    desugaredLibrarySpecification.forEachRetargetMethod(
-        method -> {
-          DexClass clazz = dexApplication.contextIndependentDefinitionFor(method.getHolderType());
-          assert clazz != null;
-          DexEncodedMethod encodedMethod = clazz.lookupMethod(method);
-          if (encodedMethod == null) {
-            // Some methods are registered but present higher in the hierarchy, ignore them.
-            return;
-          }
-          if (supported.test(encodedMethod)) {
-            supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(encodedMethod);
-          }
-        });
-
-    return new SupportedMethods(classesWithAllMethodsSupported, supportedMethods);
-  }
-
   private String lintBaseFileName(
       AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel) {
     return "desugared_apis_" + compilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
@@ -342,33 +155,38 @@
   private void writeLintFiles(
       AndroidApiLevel compilationApiLevel,
       AndroidApiLevel minApiLevel,
-      SupportedMethods supportedMethods)
+      SupportedClasses supportedClasses)
       throws Exception {
     // Build a plain text file with the desugared APIs.
     List<String> desugaredApisSignatures = new ArrayList<>();
 
     LazyLoadedDexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
-    supportedMethods.supportedMethods.forEach(
-        (clazz, methods) -> {
+    supportedClasses.forEachClass(
+        (supportedClass) -> {
           String classBinaryName =
-              DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
-          if (!supportedMethods.classesWithAllMethodsSupported.contains(clazz)) {
-            for (DexEncodedMethod method : methods) {
-              if (method.isInstanceInitializer() || method.isClassInitializer()) {
-                // No new constructors are added.
-                continue;
-              }
-              desugaredApisSignatures.add(
-                  classBinaryName
-                      + '#'
-                      + method.getReference().name
-                      + method.getReference().proto.toDescriptorString());
-            }
+              DescriptorUtils.getClassBinaryNameFromDescriptor(
+                  supportedClass.getType().descriptor.toString());
+          if (!supportedClass.getClassAnnotation().isFullySupported()) {
+            supportedClass.forEachMethodAndAnnotation(
+                (method, methodAnnotation) -> {
+                  if (method.isInstanceInitializer() || method.isClassInitializer()) {
+                    // No new constructors are added.
+                    return;
+                  }
+                  if (shouldAddMethodToLint(methodAnnotation, minApiLevel)) {
+                    desugaredApisSignatures.add(
+                        classBinaryName
+                            + '#'
+                            + method.getReference().name
+                            + method.getReference().proto.toDescriptorString());
+                  }
+                });
           } else {
             desugaredApisSignatures.add(classBinaryName);
           }
 
-          addMethodsToHeaderJar(builder, clazz, methods);
+          addMethodsToHeaderJar(
+              builder, supportedClass.getClazz(), supportedClass.getSupportedMethods());
         });
 
     // Write a plain text file with the desugared APIs.
@@ -381,7 +199,12 @@
         AppView.createForD8(
             AppInfo.createInitialAppInfo(
                 builder.build(), GlobalSyntheticsStrategy.forNonSynthesizing()));
-    CfApplicationWriter writer = new CfApplicationWriter(appView, options.getMarker());
+    Marker marker =
+        new Marker(Tool.D8)
+            .setVersion(Version.LABEL)
+            .setCompilationMode(CompilationMode.DEBUG)
+            .setBackend(Backend.CF);
+    CfApplicationWriter writer = new CfApplicationWriter(appView, marker);
     ClassFileConsumer consumer =
         new ClassFileConsumer.ArchiveConsumer(
             lintFile(compilationApiLevel, minApiLevel, FileUtils.JAR_EXTENSION));
@@ -389,491 +212,47 @@
     consumer.finished(options.reporter);
   }
 
+  private boolean shouldAddMethodToLint(
+      MethodAnnotation methodAnnotation, AndroidApiLevel minApiLevel) {
+    if (methodAnnotation == null) {
+      return true;
+    }
+    if (methodAnnotation.unsupportedInMinApiRange) {
+      // Do not lint method which are unavailable with some min apis.
+      return false;
+    }
+    if (methodAnnotation.parallelStreamMethod) {
+      return minApiLevel == AndroidApiLevel.L;
+    }
+    assert methodAnnotation.missingFromLatestAndroidJar;
+    // We add missing methods from the latest jar, javac will add the squikles.
+    return true;
+  }
+
   private void generateLintFiles(
       AndroidApiLevel compilationApiLevel,
-      Predicate<AndroidApiLevel> generateForThisMinApiLevel,
-      BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel)
+      AndroidApiLevel minApiLevel,
+      SupportedClasses supportedMethods)
       throws Exception {
     System.out.print("  - generating for min API:");
-    for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
-      if (!generateForThisMinApiLevel.test(minApiLevel)) {
-        continue;
-      }
-
-      System.out.print(" " + minApiLevel);
-
-      SupportedMethods supportedMethods =
-          collectSupportedMethods(
-              compilationApiLevel, (method -> supportedForMinApiLevel.test(minApiLevel, method)));
-      writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
-    }
-    System.out.println();
+    System.out.print(" " + minApiLevel);
+    writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
   }
 
-  private void run() throws Exception {
-    // Run over all the API levels that the desugared library can be compiled with.
-    for (int apiLevel = AndroidApiLevel.T.getLevel();
-        apiLevel >= desugaredLibrarySpecification.getRequiredCompilationApiLevel().getLevel();
-        apiLevel--) {
-      System.out.println("Generating lint files for compile API " + apiLevel);
-      run(apiLevel);
-    }
-  }
-
-  public void run(int apiLevel) throws Exception {
-    generateLintFiles(
-        AndroidApiLevel.getAndroidApiLevel(apiLevel),
-        minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B,
-        (minApiLevel, method) -> {
-          assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B;
-          if (minApiLevel == AndroidApiLevel.L) {
-            return true;
-          }
-          assert minApiLevel == AndroidApiLevel.B;
-          return !parallelMethods.contains(method.getReference());
-        });
-  }
-
-  private static DexApplication createApp(Path androidLib, InternalOptions options)
-      throws IOException {
-    AndroidApp.Builder builder = AndroidApp.builder();
-    AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
-    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
-    ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    assert !options.ignoreJavaLibraryOverride;
-    options.ignoreJavaLibraryOverride = true;
-    DexApplication app = applicationReader.read(executorService);
-    options.ignoreJavaLibraryOverride = false;
-    return app;
-  }
-
-  private static class StringBuilderWithIndent {
-
-    String NL = System.lineSeparator();
-    StringBuilder builder = new StringBuilder();
-    String indent = "";
-
-    StringBuilderWithIndent() {}
-
-    StringBuilderWithIndent indent(String indent) {
-      this.indent = indent;
-      return this;
-    }
-
-    StringBuilderWithIndent appendLineStart(String lineStart) {
-      builder.append(indent);
-      builder.append(lineStart);
-      return this;
-    }
-
-    StringBuilderWithIndent append(String string) {
-      builder.append(string);
-      return this;
-    }
-
-    StringBuilderWithIndent appendLineEnd(String lineEnd) {
-      builder.append(lineEnd);
-      builder.append(NL);
-      return this;
-    }
-
-    StringBuilderWithIndent appendLine(String line) {
-      builder.append(indent);
-      builder.append(line);
-      builder.append(NL);
-      return this;
-    }
-
-    StringBuilderWithIndent emptyLine() {
-      builder.append(NL);
-      return this;
-    }
-
-    @Override
-    public String toString() {
-      return builder.toString();
-    }
-  }
-
-  private abstract static class SourceBuilder<B extends SourceBuilder> {
-
-    protected final DexClass clazz;
-    protected final boolean newClass;
-    protected List<DexEncodedField> fields = new ArrayList<>();
-    protected List<DexEncodedMethod> constructors = new ArrayList<>();
-    protected List<DexEncodedMethod> methods = new ArrayList<>();
-
-    String className;
-    String packageName;
-
-    private SourceBuilder(DexClass clazz, boolean newClass) {
-      this.clazz = clazz;
-      this.newClass = newClass;
-      this.className = clazz.type.toSourceString();
-      int index = this.className.lastIndexOf('.');
-      this.packageName = index > 0 ? this.className.substring(0, index) : "";
-    }
-
-    public abstract B self();
-
-    private B addField(DexEncodedField field) {
-      fields.add(field);
-      return self();
-    }
-
-    private B addMethod(DexEncodedMethod method) {
-      assert !method.isClassInitializer();
-      if (method.isInitializer()) {
-        constructors.add(method);
-      } else {
-        methods.add(method);
-      }
-      return self();
-    }
-
-    protected String typeInPackage(String typeName, String packageName) {
-      if (typeName.startsWith(packageName)
-          && typeName.length() > packageName.length()
-          && typeName.charAt(packageName.length()) == '.'
-          && typeName.indexOf('.', packageName.length() + 1) == -1) {
-        return typeName.substring(packageName.length() + 1);
-      }
-      return null;
-    }
-
-    protected String typeInPackage(String typeName) {
-      String result = typeInPackage(typeName, packageName);
-      if (result == null) {
-        result = typeInPackage(typeName, "java.lang");
-      }
-      if (result == null) {
-        result = typeName;
-      }
-      return result.replace('$', '.');
-    }
-
-    protected String typeInPackage(DexType type) {
-      if (type.isPrimitiveType()) {
-        return type.toSourceString();
-      }
-      return typeInPackage(type.toSourceString());
-    }
-
-    protected String accessFlags(ClassAccessFlags accessFlags) {
-      List<String> flags = new ArrayList<>();
-      if (accessFlags.isPublic()) {
-        flags.add("public");
-      }
-      if (accessFlags.isProtected()) {
-        flags.add("protected");
-      }
-      if (accessFlags.isPrivate()) {
-        assert false;
-        flags.add("private");
-      }
-      if (accessFlags.isPackagePrivate()) {
-        assert false;
-        flags.add("/* package */");
-      }
-      if (accessFlags.isAbstract() && !accessFlags.isInterface()) {
-        flags.add("abstract");
-      }
-      if (accessFlags.isStatic()) {
-        flags.add("static");
-      }
-      if (accessFlags.isFinal()) {
-        flags.add("final");
-      }
-      return String.join(" ", flags);
-    }
-
-    protected String accessFlags(FieldAccessFlags accessFlags) {
-      List<String> flags = new ArrayList<>();
-      if (accessFlags.isPublic()) {
-        flags.add("public");
-      }
-      if (accessFlags.isProtected()) {
-        flags.add("protected");
-      }
-      if (accessFlags.isPrivate()) {
-        assert false;
-        flags.add("private");
-      }
-      if (accessFlags.isPackagePrivate()) {
-        assert false;
-        flags.add("/* package */");
-      }
-      if (accessFlags.isStatic()) {
-        flags.add("static");
-      }
-      if (accessFlags.isFinal()) {
-        flags.add("final");
-      }
-      return String.join(" ", flags);
-    }
-
-    protected String accessFlags(MethodAccessFlags accessFlags) {
-      List<String> flags = new ArrayList<>();
-      if (accessFlags.isPublic()) {
-        flags.add("public");
-      }
-      if (accessFlags.isProtected()) {
-        flags.add("protected");
-      }
-      if (accessFlags.isPrivate()) {
-        assert false;
-        flags.add("private");
-      }
-      if (accessFlags.isPackagePrivate()) {
-        assert false;
-        flags.add("/* package */");
-      }
-      if (accessFlags.isAbstract()) {
-        flags.add("abstract");
-      }
-      if (accessFlags.isStatic()) {
-        flags.add("static");
-      }
-      if (accessFlags.isFinal()) {
-        flags.add("final");
-      }
-      return String.join(" ", flags);
-    }
-
-    public String arguments(DexEncodedMethod method) {
-      DexProto proto = method.getReference().proto;
-      StringBuilder argsBuilder = new StringBuilder();
-      boolean firstArg = true;
-      int argIndex = method.isVirtualMethod() || method.accessFlags.isConstructor() ? 1 : 0;
-      int argNumber = 0;
-      argsBuilder.append("(");
-      for (DexType type : proto.parameters.values) {
-        if (!firstArg) {
-          argsBuilder.append(", ");
-        }
-        if (method.hasCode()) {
-          String name = "p" + argNumber;
-          for (LocalVariableInfo localVariable : method.getCode().asCfCode().getLocalVariables()) {
-            if (localVariable.getIndex() == argIndex) {
-              assert !localVariable.getLocal().name.toString().equals("this");
-              name = localVariable.getLocal().name.toString();
-            }
-          }
-          argsBuilder.append(typeInPackage(type)).append(" ").append(name);
-        } else {
-          argsBuilder.append(typeInPackage(type)).append(" p").append(argNumber);
-        }
-        firstArg = false;
-        argIndex += type.isWideType() ? 2 : 1;
-        argNumber++;
-      }
-      argsBuilder.append(")");
-      return argsBuilder.toString();
-    }
-  }
-
-  private static class HTMLBuilder extends StringBuilderWithIndent {
-
-    private String indent = "";
-
-    private void increaseIndent() {
-      indent += "  ";
-      indent(indent);
-    }
-
-    private void decreaseIndent() {
-      indent = indent.substring(0, indent.length() - 2);
-      indent(indent);
-    }
-
-    HTMLBuilder appendTdPackage(String s) {
-      appendLineStart("<td><code><em>" + s + "</em></code><br>");
-      if (s.startsWith("java.time")) {
-        append("<a href=\"#java-time-customizations\">See customizations</a><br");
-      } else if (s.startsWith("java.nio")) {
-        append("<a href=\"#java-nio-customizations\">See customizations</a><br");
-      }
-      return this;
-    }
-
-    HTMLBuilder appendTdClassName(String s) {
-      appendLineEnd(
-          "<code><br><br><div style=\"font-size:small;font-weight:bold;\">&nbsp;"
-              + s
-              + "</div></code><br><br></td>");
-      return this;
-    }
-
-    HTMLBuilder appendTdP(String s) {
-      appendLine("<td><p>" + s + "</p></td>");
-      return this;
-    }
-
-    HTMLBuilder appendLiCode(String s) {
-      appendLine("<li class=\"java8_table\"><code>" + s + "</code></li>");
-      return this;
-    }
-
-    HTMLBuilder start(String tag) {
-      appendLine("<" + tag + ">");
-      increaseIndent();
-      return this;
-    }
-
-    HTMLBuilder end(String tag) {
-      decreaseIndent();
-      appendLine("</" + tag + ">");
-      return this;
-    }
-  }
-
-  public static class HTMLSourceBuilder extends SourceBuilder<HTMLSourceBuilder> {
-
-    private final Set<DexMethod> parallelMethods;
-
-    public HTMLSourceBuilder(DexClass clazz, boolean newClass, Set<DexMethod> parallelMethods) {
-      super(clazz, newClass);
-      this.parallelMethods = parallelMethods;
-    }
-
-    @Override
-    public HTMLSourceBuilder self() {
-      return this;
-    }
-
-    @Override
-    public String toString() {
-      HTMLBuilder builder = new HTMLBuilder();
-      builder.start("tr");
-      if (packageName.length() > 0) {
-        builder.appendTdPackage(packageName);
-      }
-      builder.appendTdClassName(typeInPackage(className));
-      builder
-          .start("td")
-          .start(
-              "ul style=\"list-style-position:inside; list-style-type: none !important;"
-                  + " margin-left:0px;padding-left:0px !important;\"");
-      if (!fields.isEmpty()) {
-        assert newClass; // Currently no fields are added to existing classes.
-        for (DexEncodedField field : fields) {
-          builder.appendLiCode(
-              accessFlags(field.accessFlags)
-                  + " "
-                  + typeInPackage(field.getReference().type)
-                  + " "
-                  + field.getReference().name);
-        }
-      }
-      if (!constructors.isEmpty()) {
-        for (DexEncodedMethod constructor : constructors) {
-          builder.appendLiCode(
-              accessFlags(constructor.accessFlags)
-                  + " "
-                  + typeInPackage(className)
-                  + arguments(constructor));
-        }
-      }
-      List<String> parallelM = new ArrayList<>();
-      if (!methods.isEmpty()) {
-        for (DexEncodedMethod method : methods) {
-          builder.appendLiCode(
-              accessFlags(method.accessFlags)
-                  + " "
-                  + typeInPackage(method.getReference().proto.returnType)
-                  + " "
-                  + method.getReference().name
-                  + arguments(method));
-          if (parallelMethods.contains(method.getReference())) {
-            parallelM.add(method.getReference().name.toString());
-          }
-        }
-      }
-      builder.end("ul").end("td");
-      StringBuilder commentBuilder = new StringBuilder();
-      if (newClass) {
-        commentBuilder.append("Fully implemented class.");
-      } else {
-        commentBuilder.append("Additional methods on existing class.");
-      }
-      if (!parallelM.isEmpty()) {
-        commentBuilder.append(newClass ? "" : "<br>");
-        if (parallelM.size() == 1) {
-          commentBuilder
-              .append("The method <code>")
-              .append(parallelM.get(0))
-              .append("</code> is only supported from API level 21.");
-        } else {
-          commentBuilder.append("The following methods are only supported from API level 21:<br>");
-          for (int i = 0; i < parallelM.size(); i++) {
-            commentBuilder.append("<code>").append(parallelM.get(i)).append("</code><br>");
-          }
-        }
-      }
-      builder.appendTdP(commentBuilder.toString());
-      builder.end("tr");
-      return builder.toString();
-    }
-  }
-
-  private void generateClassHTML(
-      PrintStream ps,
-      DexClass clazz,
-      boolean newClass,
-      Predicate<DexEncodedField> fieldsFilter,
-      Predicate<DexEncodedMethod> methodsFilter) {
-    SourceBuilder builder = new HTMLSourceBuilder(clazz, newClass, parallelMethods);
-    StreamSupport.stream(clazz.fields().spliterator(), false)
-        .filter(fieldsFilter)
-        .filter(field -> field.accessFlags.isPublic() || field.accessFlags.isProtected())
-        .sorted(Comparator.comparing(DexEncodedField::toSourceString))
-        .forEach(builder::addField);
-    StreamSupport.stream(clazz.methods().spliterator(), false)
-        .filter(methodsFilter)
-        .filter(
-            method ->
-                (method.accessFlags.isPublic() || method.accessFlags.isProtected())
-                    && !method.accessFlags.isBridge())
-        .sorted(Comparator.comparing(DexEncodedMethod::toSourceString))
-        .forEach(builder::addMethod);
-    ps.println(builder);
-  }
-
-  private void generateDesugaredLibraryApisDocumetation() throws Exception {
-    PrintStream ps = new PrintStream(Files.newOutputStream(outputDirectory.resolve("apis.html")));
-    // Full classes added.
-    SupportedMethods supportedMethods = collectSupportedMethods(AndroidApiLevel.Q, x -> true);
-    supportedMethods.classesWithAllMethodsSupported.stream()
-        .sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
-        .forEach(clazz -> generateClassHTML(ps, clazz, true, field -> true, method -> true));
-
-    // Methods added to existing classes.
-    supportedMethods.supportedMethods.keySet().stream()
-        .filter(clazz -> !supportedMethods.classesWithAllMethodsSupported.contains(clazz))
-        .sorted(Comparator.comparing(clazz -> clazz.type.toSourceString()))
-        .forEach(
-            clazz ->
-                generateClassHTML(
-                    ps,
-                    clazz,
-                    false,
-                    field -> false,
-                    method -> supportedMethods.supportedMethods.get(clazz).contains(method)));
+  @Override
+  public AndroidApiLevel run() throws Exception {
+    AndroidApiLevel compilationLevel =
+        desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+    SupportedClasses supportedMethods =
+        new SupportedMethodsGenerator(options)
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+    System.out.println("Generating lint files for compile API " + compilationLevel);
+    generateLintFiles(compilationLevel, AndroidApiLevel.B, supportedMethods);
+    generateLintFiles(compilationLevel, AndroidApiLevel.L, supportedMethods);
+    return compilationLevel;
   }
 
   public static void main(String[] args) throws Exception {
-    if (args.length == 3) {
-      new GenerateLintFiles(args[0], args[1], args[2]).run();
-      return;
-    }
-    if (args.length == 4 && args[0].equals("--generate-api-docs")) {
-      new GenerateLintFiles(args[1], args[2], args[3]).generateDesugaredLibraryApisDocumetation();
-      return;
-    }
-    throw new RuntimeException(
-        StringUtils.joinLines(
-            "Invalid invocation.",
-            "Usage: GenerateLineFiles [--generate-api-docs] "
-                + "<desugar configuration> <desugar implementation> <output directory>"));
+    AbstractGenerateFiles.main(args);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
new file mode 100644
index 0000000..3c1cfd6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -0,0 +1,332 @@
+// Copyright (c) 2023, 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.desugaredlibrary.lint;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class SupportedClasses {
+  private final Map<DexType, SupportedClass> supportedClasses;
+
+  public void forEachClass(Consumer<SupportedClass> consumer) {
+    supportedClasses.values().forEach(consumer);
+  }
+
+  SupportedClasses(Map<DexType, SupportedClass> supportedClasses) {
+    this.supportedClasses = supportedClasses;
+  }
+
+  public static class SupportedClass {
+
+    private final DexClass clazz;
+    private final ClassAnnotation classAnnotation;
+    private final List<DexEncodedMethod> supportedMethods;
+    private final Map<DexMethod, MethodAnnotation> methodAnnotations;
+
+    private SupportedClass(
+        DexClass clazz,
+        ClassAnnotation classAnnotation,
+        List<DexEncodedMethod> supportedMethods,
+        Map<DexMethod, MethodAnnotation> methodAnnotations) {
+      this.clazz = clazz;
+      this.classAnnotation = classAnnotation;
+      this.supportedMethods = supportedMethods;
+      this.methodAnnotations = methodAnnotations;
+    }
+
+    public DexType getType() {
+      return clazz.type;
+    }
+
+    public DexClass getClazz() {
+      return clazz;
+    }
+
+    public ClassAnnotation getClassAnnotation() {
+      return classAnnotation;
+    }
+
+    public List<DexEncodedMethod> getSupportedMethods() {
+      return supportedMethods;
+    }
+
+    public void forEachMethodAndAnnotation(
+        BiConsumer<DexEncodedMethod, MethodAnnotation> biConsumer) {
+      for (DexEncodedMethod supportedMethod : supportedMethods) {
+        biConsumer.accept(supportedMethod, getMethodAnnotation(supportedMethod.getReference()));
+      }
+    }
+
+    public MethodAnnotation getMethodAnnotation(DexMethod method) {
+      return methodAnnotations.get(method);
+    }
+
+    static Builder builder(DexClass clazz) {
+      return new Builder(clazz);
+    }
+
+    private static class Builder {
+
+      private final DexClass clazz;
+      private ClassAnnotation classAnnotation;
+      private final List<DexEncodedMethod> supportedMethods = new ArrayList<>();
+      private final Map<DexMethod, MethodAnnotation> methodAnnotations = new HashMap<>();
+
+      private Builder(DexClass clazz) {
+        this.clazz = clazz;
+      }
+
+      void forEachMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
+        biConsumer.accept(clazz, supportedMethods);
+      }
+
+      void forEachMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
+        for (DexEncodedMethod dexEncodedMethod : supportedMethods) {
+          biConsumer.accept(clazz, dexEncodedMethod);
+        }
+      }
+
+      void addSupportedMethod(DexEncodedMethod method) {
+        assert method.getHolderType() == clazz.type;
+        supportedMethods.add(method);
+      }
+
+      void annotateClass(ClassAnnotation annotation) {
+        assert annotation != null;
+        assert classAnnotation == null;
+        classAnnotation = annotation;
+      }
+
+      void annotateMethod(DexMethod method, MethodAnnotation annotation) {
+        assert method.getHolderType() == clazz.type;
+        MethodAnnotation prev =
+            methodAnnotations.getOrDefault(method, MethodAnnotation.getDefault());
+        methodAnnotations.put(method, annotation.combine(prev));
+      }
+
+      MethodAnnotation getMethodAnnotation(DexMethod method) {
+        return methodAnnotations.get(method);
+      }
+
+      SupportedClass build() {
+        supportedMethods.sort(Comparator.comparing(DexEncodedMethod::getReference));
+        return new SupportedClass(
+            clazz, classAnnotation, ImmutableList.copyOf(supportedMethods), methodAnnotations);
+      }
+    }
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  static class Builder {
+
+    Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
+
+    void forEachClassAndMethods(BiConsumer<DexClass, List<DexEncodedMethod>> biConsumer) {
+      supportedClassBuilders
+          .values()
+          .forEach(classBuilder -> classBuilder.forEachMethods(biConsumer));
+    }
+
+    void forEachClassAndMethod(BiConsumer<DexClass, DexEncodedMethod> biConsumer) {
+      supportedClassBuilders
+          .values()
+          .forEach(classBuilder -> classBuilder.forEachMethod(biConsumer));
+    }
+
+    void addSupportedMethod(DexClass holder, DexEncodedMethod method) {
+      SupportedClass.Builder classBuilder =
+          supportedClassBuilders.computeIfAbsent(
+              holder.type, clazz -> SupportedClass.builder(holder));
+      classBuilder.addSupportedMethod(method);
+    }
+
+    void annotateClass(DexType type, ClassAnnotation annotation) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(type);
+      assert classBuilder != null;
+      classBuilder.annotateClass(annotation);
+    }
+
+    void annotateMethod(DexMethod method, MethodAnnotation annotation) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
+      assert classBuilder != null;
+      classBuilder.annotateMethod(method, annotation);
+    }
+
+    void annotateMethodIfPresent(DexMethod method, MethodAnnotation annotation) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
+      if (classBuilder == null) {
+        return;
+      }
+      annotateMethod(method, annotation);
+    }
+
+    MethodAnnotation getMethodAnnotation(DexMethod method) {
+      SupportedClass.Builder classBuilder = supportedClassBuilders.get(method.getHolderType());
+      assert classBuilder != null;
+      return classBuilder.getMethodAnnotation(method);
+    }
+
+    SupportedClasses build() {
+      Map<DexType, SupportedClass> map = new IdentityHashMap<>();
+      supportedClassBuilders.forEach(
+          (type, classBuilder) -> {
+            map.put(type, classBuilder.build());
+          });
+      return new SupportedClasses(ImmutableSortedMap.copyOf(map));
+    }
+  }
+
+  static class ClassAnnotation {
+
+    private final boolean fullySupported;
+    // Methods in latest android.jar but unsupported.
+    private final List<DexMethod> unsupportedMethods;
+
+    public ClassAnnotation(boolean fullySupported, List<DexMethod> unsupportedMethods) {
+      this.fullySupported = fullySupported;
+      unsupportedMethods.sort(Comparator.naturalOrder());
+      this.unsupportedMethods = ImmutableList.copyOf(unsupportedMethods);
+    }
+
+    public boolean isFullySupported() {
+      return fullySupported;
+    }
+
+    public List<DexMethod> getUnsupportedMethods() {
+      return unsupportedMethods;
+    }
+  }
+
+  public static class MethodAnnotation {
+
+    private static final MethodAnnotation COVARIANT_RETURN_SUPPORTED =
+        new MethodAnnotation(false, false, true, false, -1, -1);
+    private static final MethodAnnotation DEFAULT =
+        new MethodAnnotation(false, false, false, false, -1, -1);
+    private static final MethodAnnotation PARALLEL_STREAM_METHOD =
+        new MethodAnnotation(true, false, false, false, -1, -1);
+    private static final MethodAnnotation MISSING_FROM_LATEST_ANDROID_JAR =
+        new MethodAnnotation(false, true, false, false, -1, -1);
+
+    // ParallelStream methods are not supported when the runtime api level is strictly below 21.
+    final boolean parallelStreamMethod;
+    // Methods not in the latest android jar but still fully supported.
+    final boolean missingFromLatestAndroidJar;
+    // Methods not supported in a given min api range.
+    final boolean unsupportedInMinApiRange;
+    final boolean covariantReturnSupported;
+    final int minRange;
+    final int maxRange;
+
+    MethodAnnotation(
+        boolean parallelStreamMethod,
+        boolean missingFromLatestAndroidJar,
+        boolean covariantReturnSupported,
+        boolean unsupportedInMinApiRange,
+        int minRange,
+        int maxRange) {
+      this.parallelStreamMethod = parallelStreamMethod;
+      this.missingFromLatestAndroidJar = missingFromLatestAndroidJar;
+      this.covariantReturnSupported = covariantReturnSupported;
+      this.unsupportedInMinApiRange = unsupportedInMinApiRange;
+      this.minRange = minRange;
+      this.maxRange = maxRange;
+    }
+
+    public static MethodAnnotation getCovariantReturnSupported() {
+      return COVARIANT_RETURN_SUPPORTED;
+    }
+
+    public static MethodAnnotation getDefault() {
+      return DEFAULT;
+    }
+
+    public static MethodAnnotation getParallelStreamMethod() {
+      return PARALLEL_STREAM_METHOD;
+    }
+
+    public static MethodAnnotation getMissingFromLatestAndroidJar() {
+      return MISSING_FROM_LATEST_ANDROID_JAR;
+    }
+
+    public static MethodAnnotation createMissingInMinApi(int api) {
+      return new MethodAnnotation(false, false, false, true, api, api);
+    }
+
+    public boolean isUnsupportedInMinApiRange() {
+      return unsupportedInMinApiRange;
+    }
+
+    public int getMinRange() {
+      return minRange;
+    }
+
+    public int getMaxRange() {
+      return maxRange;
+    }
+
+    public boolean isCovariantReturnSupported() {
+      return covariantReturnSupported;
+    }
+
+    public MethodAnnotation combine(MethodAnnotation other) {
+      if (this == getDefault()) {
+        return other;
+      }
+      if (other == getDefault()) {
+        return this;
+      }
+      int newMin, newMax;
+      if (!unsupportedInMinApiRange && !other.unsupportedInMinApiRange) {
+        newMin = newMax = -1;
+      } else if (!unsupportedInMinApiRange || !other.unsupportedInMinApiRange) {
+        newMin = unsupportedInMinApiRange ? minRange : other.minRange;
+        newMax = unsupportedInMinApiRange ? maxRange : other.maxRange;
+      } else {
+        // Merge ranges if contiguous or throw.
+        if (maxRange == other.minRange - 1) {
+          newMin = minRange;
+          newMax = other.maxRange;
+        } else if (other.maxRange == minRange - 1) {
+          newMin = other.minRange;
+          newMax = maxRange;
+        } else {
+          // 20 is missing, so if maxRange or minRange are 19 the following is 21.
+          if (maxRange == 19 && other.minRange == 21) {
+            newMin = minRange;
+            newMax = other.maxRange;
+          } else if (other.maxRange == 19 && minRange == 21) {
+            newMin = other.minRange;
+            newMax = maxRange;
+          } else {
+            throw new RuntimeException("Cannot merge ranges.");
+          }
+        }
+      }
+      return new MethodAnnotation(
+          parallelStreamMethod || other.parallelStreamMethod,
+          missingFromLatestAndroidJar || other.missingFromLatestAndroidJar,
+          covariantReturnSupported || other.covariantReturnSupported,
+          unsupportedInMinApiRange || other.unsupportedInMinApiRange,
+          newMin,
+          newMax);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
new file mode 100644
index 0000000..752262f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedMethodsGenerator.java
@@ -0,0 +1,372 @@
+// Copyright (c) 2023, 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.desugaredlibrary.lint;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.lint.AbstractGenerateFiles.MAX_TESTED_ANDROID_API_LEVEL;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.ClassAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+public class SupportedMethodsGenerator {
+
+  private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
+
+  private final InternalOptions options;
+
+  public SupportedMethodsGenerator(InternalOptions options) {
+    this.options = options;
+  }
+
+  public SupportedClasses run(Collection<Path> desugaredLibraryImplementation, Path specification)
+      throws IOException {
+    SupportedClasses.Builder builder = SupportedClasses.builder();
+    // First analyze everything which is supported when desugaring for api 1.
+    collectSupportedMethodsInB(desugaredLibraryImplementation, specification, builder);
+    // Second annotate all apis which are partially and/or fully supported.
+    AndroidApp library =
+        AndroidApp.builder()
+            .addProgramFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL))
+            .build();
+    DirectMappedDexApplication appForMax =
+        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+    annotateMethodsNotOnLatestAndroidJar(appForMax, builder);
+    annotateParallelMethods(builder);
+    annotatePartialDesugaringMethods(builder, specification);
+    annotateClasses(builder, appForMax);
+    return builder.build();
+  }
+
+  private void annotateClasses(
+      SupportedClasses.Builder builder, DirectMappedDexApplication appForMax) {
+
+    builder.forEachClassAndMethods(
+        (clazz, methods) -> {
+          DexClass maxClass = appForMax.definitionFor(clazz.type);
+          List<DexMethod> missing = new ArrayList<>();
+          boolean fullySupported = true;
+          for (DexEncodedMethod method : maxClass.methods()) {
+            if (!(method.isPublic() || method.isProtectedMethod())) {
+              continue;
+            }
+            // If the method is in android.jar but not in the desugared library, or annotated, then
+            // the class is not marked as fully supported.
+            if (methods.stream().noneMatch(em -> em.getReference() == method.getReference())) {
+              missing.add(method.getReference());
+              fullySupported = false;
+            }
+            MethodAnnotation methodAnnotation = builder.getMethodAnnotation(method.getReference());
+            if (methodAnnotation != null && !methodAnnotation.isCovariantReturnSupported()) {
+              fullySupported = false;
+            }
+          }
+          builder.annotateClass(clazz.type, new ClassAnnotation(fullySupported, missing));
+        });
+  }
+
+  private void annotatePartialDesugaringMethods(
+      SupportedClasses.Builder builder, Path specification) throws IOException {
+    for (int api = AndroidApiLevel.K.getLevel();
+        api <= MAX_TESTED_ANDROID_API_LEVEL.getLevel();
+        api++) {
+      if (api == 20) {
+        // Missing android.jar.
+        continue;
+      }
+      AndroidApiLevel androidApiLevel = AndroidApiLevel.getAndroidApiLevel(api);
+      AndroidApp library =
+          AndroidApp.builder().addProgramFiles(getAndroidJarPath(androidApiLevel)).build();
+      DirectMappedDexApplication dexApplication =
+          new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+      AppInfoWithClassHierarchy appInfo =
+          AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+              dexApplication,
+              ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+              MainDexInfo.none(),
+              GlobalSyntheticsStrategy.forNonSynthesizing(),
+              StartupOrder.empty());
+      MachineDesugaredLibrarySpecification machineSpecification =
+          getMachineSpecification(androidApiLevel, specification);
+
+      options.setMinApiLevel(androidApiLevel);
+      options.resetDesugaredLibrarySpecificationForTesting();
+      options.setDesugaredLibrarySpecification(machineSpecification);
+      List<DexMethod> backports =
+          BackportedMethodRewriter.generateListOfBackportedMethods(
+              library, options, ThreadUtils.getExecutorService(1));
+
+      int finalApi = api;
+      builder.forEachClassAndMethod(
+          (clazz, encodedMethod) -> {
+            DexMethod dexMethod = encodedMethod.getReference();
+            if (machineSpecification.isSupported(dexMethod)
+                || backports.contains(dexMethod)
+                || machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
+              if (machineSpecification.getCovariantRetarget().containsKey(dexMethod)) {
+                builder.annotateMethod(dexMethod, MethodAnnotation.getCovariantReturnSupported());
+              }
+              return;
+            }
+            if (machineSpecification.getEmulatedInterfaces().containsKey(dexMethod.getHolderType())
+                && encodedMethod.isStatic()) {
+              // Static methods on emulated interfaces are always supported if the emulated
+              // interface is
+              // supported.
+              return;
+            }
+            MethodResolutionResult methodResolutionResult =
+                appInfo.resolveMethod(
+                    dexMethod,
+                    appInfo
+                        .contextIndependentDefinitionFor(dexMethod.getHolderType())
+                        .isInterface());
+            if (methodResolutionResult.isFailedResolution()) {
+              builder.annotateMethod(dexMethod, MethodAnnotation.createMissingInMinApi(finalApi));
+            }
+          });
+    }
+  }
+
+  private void annotateParallelMethods(SupportedClasses.Builder builder) {
+    for (DexMethod parallelMethod : getParallelMethods()) {
+      builder.annotateMethodIfPresent(parallelMethod, MethodAnnotation.getParallelStreamMethod());
+    }
+  }
+
+  private void annotateMethodsNotOnLatestAndroidJar(
+      DirectMappedDexApplication appForMax, SupportedClasses.Builder builder) {
+    builder.forEachClassAndMethod(
+        (clazz, method) -> {
+          DexClass dexClass = appForMax.definitionFor(clazz.type);
+          assert dexClass != null;
+          if (dexClass.lookupMethod(method.getReference()) == null) {
+            builder.annotateMethod(
+                method.getReference(), MethodAnnotation.getMissingFromLatestAndroidJar());
+          }
+        });
+  }
+
+  static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
+    String jar =
+        apiLevel == AndroidApiLevel.MASTER
+            ? "third_party/android_jar/lib-master/android.jar"
+            : String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel());
+    return Paths.get(jar);
+  }
+
+  private void collectSupportedMethodsInB(
+      Collection<Path> desugaredLibraryImplementation,
+      Path specification,
+      SupportedClasses.Builder builder)
+      throws IOException {
+
+    AndroidApp implementation =
+        AndroidApp.builder().addProgramFiles(desugaredLibraryImplementation).build();
+    DirectMappedDexApplication implementationApplication =
+        new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
+
+    AndroidApp library =
+        AndroidApp.builder()
+            .addLibraryFiles(getAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL))
+            .build();
+    DirectMappedDexApplication amendedAppForMax =
+        new ApplicationReader(library, options, Timing.empty()).read().toDirect();
+
+    MachineDesugaredLibrarySpecification machineSpecification =
+        getMachineSpecification(AndroidApiLevel.B, specification);
+
+    options.setMinApiLevel(AndroidApiLevel.B);
+    options.resetDesugaredLibrarySpecificationForTesting();
+    options.setDesugaredLibrarySpecification(machineSpecification);
+    List<DexMethod> backports =
+        BackportedMethodRewriter.generateListOfBackportedMethods(
+            library, options, ThreadUtils.getExecutorService(1));
+
+    DesugaredLibraryAmender.run(
+        machineSpecification.getAmendLibraryMethods(),
+        machineSpecification.getAmendLibraryFields(),
+        amendedAppForMax,
+        options.reporter,
+        ComputedApiLevel.unknown());
+
+    for (DexProgramClass clazz : implementationApplication.classes()) {
+      // All emulated interfaces static and default methods are supported.
+      if (machineSpecification.getEmulatedInterfaces().containsKey(clazz.type)) {
+        assert clazz.isInterface();
+        for (DexEncodedMethod method : clazz.methods()) {
+          if (!method.isDefaultMethod() && !method.isStatic()) {
+            continue;
+          }
+          if (method.getName().startsWith("lambda$")
+              || method.getName().toString().contains("$deserializeLambda$")) {
+            // We don't care if lambda methods are present or not.
+            continue;
+          }
+          if (method
+              .getReference()
+              .toSourceString()
+              .equals("void java.util.Collection.forEach(java.util.function.Consumer)")) {
+            // This method is present for binary compatibility. Do not mark as supported (Supported
+            // through Iterable#forEach).
+            continue;
+          }
+          builder.addSupportedMethod(clazz, method);
+        }
+        addBackports(clazz, backports, builder, amendedAppForMax);
+      } else {
+        // All methods in maintained or rewritten classes are supported.
+        if ((clazz.accessFlags.isPublic() || clazz.accessFlags.isProtected())
+            && machineSpecification.isContextTypeMaintainedOrRewritten(clazz.type)
+            && amendedAppForMax.definitionFor(clazz.type) != null) {
+          for (DexEncodedMethod method : clazz.methods()) {
+            if (!method.isPublic() && !method.isProtectedMethod()) {
+              continue;
+            }
+            builder.addSupportedMethod(clazz, method);
+          }
+          addBackports(clazz, backports, builder, amendedAppForMax);
+        }
+      }
+    }
+
+    // All retargeted methods are supported.
+    machineSpecification.forEachRetargetMethod(
+        method -> {
+          DexClass dexClass = implementationApplication.definitionFor(method.getHolderType());
+          if (dexClass != null) {
+            DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
+            if (dexEncodedMethod != null) {
+              builder.addSupportedMethod(dexClass, dexEncodedMethod);
+              return;
+            }
+          }
+          dexClass = amendedAppForMax.definitionFor(method.getHolderType());
+          DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
+          assert dexEncodedMethod != null;
+          builder.addSupportedMethod(dexClass, dexEncodedMethod);
+        });
+  }
+
+  private void addBackports(
+      DexProgramClass clazz,
+      List<DexMethod> backports,
+      SupportedClasses.Builder builder,
+      DirectMappedDexApplication amendedAppForMax) {
+    for (DexMethod backport : backports) {
+      if (clazz.type == backport.getHolderType()) {
+        DexClass maxClass = amendedAppForMax.definitionFor(clazz.type);
+        DexEncodedMethod dexEncodedMethod = maxClass.lookupMethod(backport);
+        // There is a single backport not in amendedAppForMax, Stream#ofNullable.
+        assert dexEncodedMethod != null
+            || backport
+                .toString()
+                .equals(
+                    "java.util.stream.Stream java.util.stream.Stream.ofNullable(java.lang.Object)");
+        if (dexEncodedMethod == null) {
+          dexEncodedMethod =
+              DexEncodedMethod.builder()
+                  .setMethod(backport)
+                  .setAccessFlags(
+                      MethodAccessFlags.fromSharedAccessFlags(
+                          Constants.ACC_PUBLIC | Constants.ACC_STATIC, false))
+                  .build();
+        }
+        builder.addSupportedMethod(clazz, dexEncodedMethod);
+      }
+    }
+  }
+
+  private MachineDesugaredLibrarySpecification getMachineSpecification(
+      AndroidApiLevel api, Path specification) throws IOException {
+    DesugaredLibrarySpecification librarySpecification =
+        DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
+            StringResource.fromFile(specification),
+            options.itemFactory,
+            options.reporter,
+            false,
+            api.getLevel());
+    Path androidJarPath = getAndroidJarPath(librarySpecification.getRequiredCompilationApiLevel());
+    DexApplication app = createLoadingApp(androidJarPath, options);
+    return librarySpecification.toMachineSpecification(app, Timing.empty());
+  }
+
+  private DexApplication createLoadingApp(Path androidLib, InternalOptions options)
+      throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
+    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, Timing.empty());
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    assert !options.ignoreJavaLibraryOverride;
+    options.ignoreJavaLibraryOverride = true;
+    DexApplication loadingApp = applicationReader.read(executorService);
+    options.ignoreJavaLibraryOverride = false;
+    return loadingApp;
+  }
+
+  private Set<DexMethod> getParallelMethods() {
+    Set<DexMethod> parallelMethods = Sets.newIdentityHashSet();
+    DexItemFactory factory = options.dexItemFactory();
+    DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;"));
+    DexMethod parallelMethod =
+        factory.createMethod(
+            factory.collectionType,
+            factory.createProto(streamType),
+            factory.createString("parallelStream"));
+    parallelMethods.add(parallelMethod);
+    DexType baseStreamType =
+        factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
+    for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
+      streamType =
+          factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
+      parallelMethod =
+          factory.createMethod(
+              streamType, factory.createProto(streamType), factory.createString("parallel"));
+      parallelMethods.add(parallelMethod);
+      // Also filter out the generated bridges for the covariant return type.
+      parallelMethod =
+          factory.createMethod(
+              streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
+      parallelMethods.add(parallelMethod);
+    }
+    return parallelMethods;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
index b288d9a..2ac41e4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
@@ -162,9 +162,8 @@
     //   pop exception result
     //   [push fake result for <method>]
     UtilityMethodForCodeOptimizations throwMethod =
-        methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
+        methodSynthesizerConsumer.synthesizeMethod(appView, eventConsumer, methodProcessingContext);
     ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
-    eventConsumer.acceptThrowMethod(throwProgramMethod, context);
 
     ArrayList<CfInstruction> replacement = new ArrayList<>();
     DexTypeList parameters = invoke.getMethod().getParameters();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 0c5ed70..32e2c37 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -807,15 +807,7 @@
     }
     for (Wrapper<DexMethod> wrapper : signatures.signatures) {
       resolveForwardForSignature(
-          clazz,
-          wrapper.get(),
-          (target, forward) -> {
-            if (isLiveMethod(target) && !superInfo.isTargetedByForwards(target)) {
-              additionalForwards.add(target);
-              addForwardingMethod(target, forward, clazz);
-            }
-          },
-          eventConsumer);
+          clazz, superInfo, additionalForwards, wrapper.get(), eventConsumer);
     }
   }
 
@@ -823,8 +815,9 @@
   // the 'addForward' call-back is called with the target of the forward.
   private void resolveForwardForSignature(
       DexClass clazz,
+      ClassInfo superInfo,
+      Builder<DexClassAndMethod> additionalForwards,
       DexMethod method,
-      BiConsumer<DexClassAndMethod, DexMethod> addForward,
       InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnLegacy(clazz, method);
@@ -891,23 +884,30 @@
     DexClassAndMethod virtualDispatchTarget = lookupMethodTarget.getTarget();
     assert virtualDispatchTarget != null;
 
-    // If resolution targets a default interface method, forward it.
-    if (virtualDispatchTarget.isDefaultMethod()) {
-      addForward.accept(
-          virtualDispatchTarget,
-          helper
-              .ensureDefaultAsMethodOfCompanionClassStub(virtualDispatchTarget, eventConsumer)
-              .getReference());
+    if (!isLiveMethod(virtualDispatchTarget)
+        || superInfo.isTargetedByForwards(virtualDispatchTarget)) {
       return;
     }
 
-    DerivedMethod forwardingMethod =
-        helper.computeEmulatedInterfaceForwardingMethod(
-            virtualDispatchTarget.getHolder(), virtualDispatchTarget);
-    if (forwardingMethod != null) {
-      DexMethod concreteForwardingMethod =
-          helper.ensureEmulatedInterfaceForwardingMethod(forwardingMethod, eventConsumer);
-      addForward.accept(virtualDispatchTarget, concreteForwardingMethod);
+    // If resolution targets a default interface method, forward it.
+    DexMethod forward = null;
+    if (virtualDispatchTarget.isDefaultMethod()) {
+      forward =
+          helper
+              .ensureDefaultAsMethodOfCompanionClassStub(virtualDispatchTarget, eventConsumer)
+              .getReference();
+    } else {
+      DerivedMethod forwardingMethod =
+          helper.computeEmulatedInterfaceForwardingMethod(
+              virtualDispatchTarget.getHolder(), virtualDispatchTarget);
+      if (forwardingMethod != null) {
+        forward = helper.ensureEmulatedInterfaceForwardingMethod(forwardingMethod, eventConsumer);
+      }
+    }
+
+    if (forward != null) {
+      additionalForwards.add(virtualDispatchTarget);
+      addForwardingMethod(virtualDispatchTarget, forward, clazz);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index afae01d..2140c13 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
 import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
@@ -59,7 +60,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 import org.objectweb.asm.Opcodes;
@@ -103,22 +103,26 @@
     CfCode cfCode = method.getDefinition().getCode().asCfCode();
     for (CfInstruction instruction : cfCode.getInstructions()) {
       if (instruction.isInvokeDynamic() && needsDesugaring(instruction, method)) {
-        prepareInvokeDynamicOnRecord(instruction.asInvokeDynamic(), programAdditions, method);
+        prepareInvokeDynamicOnRecord(
+            instruction.asInvokeDynamic(), programAdditions, method, eventConsumer);
       }
     }
   }
 
   private void prepareInvokeDynamicOnRecord(
-      CfInvokeDynamic invokeDynamic, ProgramAdditions programAdditions, ProgramMethod context) {
+      CfInvokeDynamic invokeDynamic,
+      ProgramAdditions programAdditions,
+      ProgramMethod context,
+      RecordInstructionDesugaringEventConsumer eventConsumer) {
     RecordInvokeDynamic recordInvokeDynamic =
         parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
     if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName
         || recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
-      ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions);
+      ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions, context, eventConsumer);
       return;
     }
     if (recordInvokeDynamic.getMethodName() == factory.equalsMethodName) {
-      ensureEqualsRecord(recordInvokeDynamic, programAdditions);
+      ensureEqualsRecord(recordInvokeDynamic, programAdditions, context, eventConsumer);
       return;
     }
     throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared.");
@@ -207,11 +211,19 @@
         parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
     if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName) {
       return desugarInvokeRecordToString(
-          recordInvokeDynamic, localStackAllocator, eventConsumer, methodProcessingContext);
+          recordInvokeDynamic,
+          localStackAllocator,
+          eventConsumer,
+          context,
+          methodProcessingContext);
     }
     if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
       return desugarInvokeRecordHashCode(
-          recordInvokeDynamic, localStackAllocator, eventConsumer, methodProcessingContext);
+          recordInvokeDynamic,
+          localStackAllocator,
+          eventConsumer,
+          context,
+          methodProcessingContext);
     }
     if (recordInvokeDynamic.getMethodName() == factory.equalsMethodName) {
       return desugarInvokeRecordEquals(recordInvokeDynamic);
@@ -251,24 +263,37 @@
   }
 
   private DexMethod ensureEqualsRecord(
-      RecordInvokeDynamic recordInvokeDynamic, ProgramAdditions programAdditions) {
-    DexMethod getFieldsAsObjects = ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions);
+      RecordInvokeDynamic recordInvokeDynamic,
+      ProgramAdditions programAdditions,
+      ProgramMethod context,
+      RecordInstructionDesugaringEventConsumer eventConsumer) {
+    DexMethod getFieldsAsObjects =
+        ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions, context, eventConsumer);
     DexProgramClass clazz = recordInvokeDynamic.getRecordClass();
     DexMethod method = equalsRecordMethod(clazz.type);
     assert clazz.lookupProgramMethod(method) == null;
-    programAdditions.ensureMethod(
-        method, () -> synthesizeEqualsRecordMethod(clazz, getFieldsAsObjects, method));
+    ProgramMethod equalsHelperMethod =
+        programAdditions.ensureMethod(
+            method, () -> synthesizeEqualsRecordMethod(clazz, getFieldsAsObjects, method));
+    eventConsumer.acceptRecordEqualsHelperMethod(equalsHelperMethod, context);
     return method;
   }
 
   private DexMethod ensureGetFieldsAsObjects(
-      RecordInvokeDynamic recordInvokeDynamic, ProgramAdditions programAdditions) {
+      RecordInvokeDynamic recordInvokeDynamic,
+      ProgramAdditions programAdditions,
+      ProgramMethod context,
+      RecordInstructionDesugaringEventConsumer eventConsumer) {
     DexProgramClass clazz = recordInvokeDynamic.getRecordClass();
     DexMethod method = getFieldsAsObjectsMethod(clazz.type);
     assert clazz.lookupProgramMethod(method) == null;
-    programAdditions.ensureMethod(
-        method,
-        () -> synthesizeGetFieldsAsObjectsMethod(clazz, recordInvokeDynamic.getFields(), method));
+    ProgramMethod getFieldsAsObjectsHelperMethod =
+        programAdditions.ensureMethod(
+            method,
+            () ->
+                synthesizeGetFieldsAsObjectsMethod(clazz, recordInvokeDynamic.getFields(), method));
+    eventConsumer.acceptRecordGetFieldsAsObjectsHelperMethod(
+        getFieldsAsObjectsHelperMethod, context);
     return method;
   }
 
@@ -306,6 +331,7 @@
       RecordInvokeDynamic recordInvokeDynamic,
       LocalStackAllocator localStackAllocator,
       RecordInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
     localStackAllocator.allocateLocalStack(1);
     DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
@@ -320,7 +346,7 @@
             recordHashCodeHelperProto,
             RecordCfMethods::RecordMethods_hashCode,
             methodProcessingContext);
-    eventConsumer.acceptRecordMethod(programMethod);
+    eventConsumer.acceptRecordHashCodeHelperMethod(programMethod, context);
     instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
     return instructions;
   }
@@ -335,6 +361,7 @@
       RecordInvokeDynamic recordInvokeDynamic,
       LocalStackAllocator localStackAllocator,
       RecordInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
     localStackAllocator.allocateLocalStack(2);
     DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
@@ -357,7 +384,7 @@
             recordToStringHelperProto,
             RecordCfMethods::RecordMethods_toString,
             methodProcessingContext);
-    eventConsumer.acceptRecordMethod(programMethod);
+    eventConsumer.acceptRecordToStringHelperMethod(programMethod, context);
     instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
     return instructions;
   }
@@ -374,6 +401,21 @@
     return false;
   }
 
+  private void ensureRecordClass(
+      RecordInstructionDesugaringEventConsumer eventConsumer, ProgramMethod context) {
+    DexProgramClass recordTagClass =
+        internalEnsureRecordClass(eventConsumer, ImmutableList.of(context));
+    eventConsumer.acceptRecordClassContext(recordTagClass, context);
+  }
+
+  private void ensureRecordClass(
+      RecordClassSynthesizerDesugaringEventConsumer eventConsumer,
+      Collection<DexProgramClass> recordClasses) {
+    DexProgramClass recordTagClass = internalEnsureRecordClass(eventConsumer, recordClasses);
+    recordClasses.forEach(
+        recordClass -> eventConsumer.acceptRecordClassContext(recordTagClass, recordClass));
+  }
+
   /**
    * If java.lang.Record is referenced from a class' supertype or a program method/field signature,
    * then the global synthetic is generated upfront of the compilation to avoid confusing D8/R8.
@@ -382,11 +424,12 @@
    * contains "x instance of java.lang.Record" but no record type is present, then the global
    * synthetic is generated during instruction desugaring scanning.
    */
-  private void ensureRecordClass(
-      RecordDesugaringEventConsumer eventConsumer, Collection<ProgramDefinition> contexts) {
+  private DexProgramClass internalEnsureRecordClass(
+      RecordDesugaringEventConsumer eventConsumer,
+      Collection<? extends ProgramDefinition> contexts) {
     DexItemFactory factory = appView.dexItemFactory();
     checkRecordTagNotPresent(factory);
-    appView
+    return appView
         .getSyntheticItems()
         .ensureGlobalClass(
             () -> new MissingGlobalSyntheticsConsumerDiagnostic("Record desugaring"),
@@ -401,11 +444,6 @@
             eventConsumer::acceptRecordClass);
   }
 
-  private void ensureRecordClass(
-      RecordDesugaringEventConsumer eventConsumer, ProgramDefinition context) {
-    ensureRecordClass(eventConsumer, ImmutableList.of(context));
-  }
-
   private void checkRecordTagNotPresent(DexItemFactory factory) {
     DexClass r8RecordClass =
         appView.appInfo().definitionForWithoutExistenceAssert(factory.recordTagType);
@@ -505,7 +543,7 @@
       CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
     DexApplicationReadFlags flags = appView.appInfo().app().getFlags();
     if (flags.hasReadRecordReferenceFromProgramClass()) {
-      List<ProgramDefinition> classes = new ArrayList<>();
+      List<DexProgramClass> classes = new ArrayList<>(flags.getRecordWitnesses().size());
       for (DexType recordWitness : flags.getRecordWitnesses()) {
         DexClass dexClass = appView.contextIndependentDefinitionFor(recordWitness);
         assert dexClass != null;
@@ -520,8 +558,7 @@
   public void postProcessingDesugaring(
       Collection<DexProgramClass> programClasses,
       CfPostProcessingDesugaringEventConsumer eventConsumer,
-      ExecutorService executorService)
-      throws ExecutionException {
+      ExecutorService executorService) {
     for (DexProgramClass clazz : programClasses) {
       if (clazz.isRecord()) {
         assert clazz.superType == factory.recordType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java
index f81b5d6..469e0de 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaringEventConsumer.java
@@ -8,10 +8,23 @@
 
 public interface RecordDesugaringEventConsumer {
 
-  void acceptRecordClass(DexProgramClass recordClass);
+  void acceptRecordClass(DexProgramClass recordTagClass);
+
+  interface RecordClassSynthesizerDesugaringEventConsumer extends RecordDesugaringEventConsumer {
+
+    void acceptRecordClassContext(DexProgramClass recordTagClass, DexProgramClass recordClass);
+  }
 
   interface RecordInstructionDesugaringEventConsumer extends RecordDesugaringEventConsumer {
 
-    void acceptRecordMethod(ProgramMethod method);
+    void acceptRecordClassContext(DexProgramClass recordTagClass, ProgramMethod context);
+
+    void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context);
+
+    void acceptRecordGetFieldsAsObjectsHelperMethod(ProgramMethod method, ProgramMethod context);
+
+    void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context);
+
+    void acceptRecordToStringHelperMethod(ProgramMethod method, ProgramMethod context);
   }
 }
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 dbd19bd..509d6c1 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
@@ -1495,7 +1495,7 @@
       // Replace the check-cast instruction by throwClassCastExceptionIfNotNull().
       UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
           UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
-              appView, methodProcessingContext);
+              appView, methodProcessor.getEventConsumer(), methodProcessingContext);
       throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
       InvokeStatic replacement =
           InvokeStatic.builder()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 92e653d..b7d7b10 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -19,11 +19,12 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueIsDeadAnalysis;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterators;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
@@ -54,11 +55,12 @@
     // more dead instructions.
     Deque<BasicBlock> worklist = new ArrayDeque<>();
     do {
+      ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code);
       worklist.addAll(code.topologicallySortedBlocks());
       while (!worklist.isEmpty()) {
         BasicBlock block = worklist.removeLast();
-        removeDeadInstructions(worklist, code, block);
-        removeDeadPhis(worklist, code, block);
+        removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis);
+        removeDeadPhis(worklist, block, valueIsDeadAnalysis);
       }
     } while (codeRewriter.simplifyIf(code).anySimplifications()
         || removeUnneededCatchHandlers(code));
@@ -70,8 +72,9 @@
   public boolean verifyNoDeadCode(IRCode code) {
     assert !codeRewriter.rewriteMoveResult(code);
     assert !removeUnneededCatchHandlers(code);
+    ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code);
     for (BasicBlock block : code.blocks) {
-      assert !block.hasDeadPhi(appView, code);
+      assert !valueIsDeadAnalysis.hasDeadPhi(block);
       for (Instruction instruction : block.getInstructions()) {
         // No unused move-result instructions.
         assert !instruction.isInvoke()
@@ -79,7 +82,7 @@
             || instruction.outValue().hasAnyUsers();
         // No dead instructions.
         assert !instruction.canBeDeadCode(appView, code).isDeadIfOutValueIsDead()
-            || (instruction.hasOutValue() && !instruction.outValue().isDead(appView, code));
+            || (instruction.hasOutValue() && !valueIsDeadAnalysis.isDead(instruction.outValue()));
       }
     }
     return true;
@@ -108,11 +111,12 @@
     }
   }
 
-  private void removeDeadPhis(Queue<BasicBlock> worklist, IRCode code, BasicBlock block) {
+  private void removeDeadPhis(
+      Queue<BasicBlock> worklist, BasicBlock block, ValueIsDeadAnalysis valueIsDeadAnalysis) {
     Iterator<Phi> phiIt = block.getPhis().iterator();
     while (phiIt.hasNext()) {
       Phi phi = phiIt.next();
-      if (phi.isDead(appView, code)) {
+      if (valueIsDeadAnalysis.isDead(phi)) {
         phiIt.remove();
         for (Value operand : phi.getOperands()) {
           operand.removePhiUser(phi);
@@ -122,7 +126,11 @@
     }
   }
 
-  private void removeDeadInstructions(Queue<BasicBlock> worklist, IRCode code, BasicBlock block) {
+  private void removeDeadInstructions(
+      Queue<BasicBlock> worklist,
+      IRCode code,
+      BasicBlock block,
+      ValueIsDeadAnalysis valueIsDeadAnalysis) {
     InstructionListIterator iterator = block.listIterator(code, block.getInstructions().size());
     while (iterator.hasPrevious()) {
       Instruction current = iterator.previous();
@@ -165,7 +173,7 @@
       if (deadInstructionResult.isMaybeDead()) {
         boolean satisfied = true;
         for (Value valueRequiredToBeDead : deadInstructionResult.getValuesRequiredToBeDead()) {
-          if (!valueRequiredToBeDead.isDead(appView, code)) {
+          if (!valueIsDeadAnalysis.isDead(valueRequiredToBeDead)) {
             satisfied = false;
             break;
           }
@@ -175,7 +183,7 @@
         }
       }
       Value outValue = current.outValue();
-      if (outValue != null && !outValue.isDead(appView, code)) {
+      if (outValue != null && !valueIsDeadAnalysis.isDead(outValue)) {
         continue;
       }
       updateWorklist(worklist, current);
@@ -291,21 +299,12 @@
         }
 
         @Override
-        public boolean isDeadIfInValueIsDead() {
-          return true;
-        }
-
-        @Override
         public Iterable<Value> getValuesRequiredToBeDead() {
-          return () -> Iterators.singletonIterator(inValueRequiredToBeDead);
+          return IterableUtils.singleton(inValueRequiredToBeDead);
         }
       };
     }
 
-    public boolean isDeadIfInValueIsDead() {
-      return false;
-    }
-
     public boolean isDeadIfOutValueIsDead() {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index d4d8601..e1367b8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -23,11 +23,15 @@
   public interface MethodSynthesizerConsumer {
 
     UtilityMethodForCodeOptimizations synthesizeMethod(
-        AppView<?> appView, MethodProcessingContext methodProcessingContext);
+        AppView<?> appView,
+        UtilityMethodsForCodeOptimizationsEventConsumer eventConsumer,
+        MethodProcessingContext methodProcessingContext);
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeToStringIfNotNullMethod(
-      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      AppView<?> appView,
+      UtilityMethodsForCodeOptimizationsEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
@@ -44,6 +48,8 @@
                     .setApiLevelForCode(appView.computedMinApiLevel())
                     .setCode(method -> getToStringIfNotNullCodeTemplate(method, dexItemFactory))
                     .setProto(proto));
+    eventConsumer.acceptUtilityToStringIfNotNullMethod(
+        syntheticMethod, methodProcessingContext.getMethodContext());
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -54,7 +60,9 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowClassCastExceptionIfNotNullMethod(
-      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      AppView<?> appView,
+      UtilityMethodsForCodeOptimizationsEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
@@ -74,6 +82,8 @@
                         method ->
                             getThrowClassCastExceptionIfNotNullCodeTemplate(method, dexItemFactory))
                     .setProto(proto));
+    eventConsumer.acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+        syntheticMethod, methodProcessingContext.getMethodContext());
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -85,7 +95,9 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowIllegalAccessErrorMethod(
-      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      AppView<?> appView,
+      UtilityMethodsForCodeOptimizationsEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.illegalAccessErrorType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
@@ -103,6 +115,8 @@
                     .setCode(
                         method -> getThrowIllegalAccessErrorCodeTemplate(method, dexItemFactory))
                     .setProto(proto));
+    eventConsumer.acceptUtilityThrowIllegalAccessErrorMethod(
+        syntheticMethod, methodProcessingContext.getMethodContext());
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -114,7 +128,9 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowIncompatibleClassChangeErrorMethod(
-      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      AppView<?> appView,
+      UtilityMethodsForCodeOptimizationsEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.icceType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
@@ -134,6 +150,8 @@
                             getThrowIncompatibleClassChangeErrorCodeTemplate(
                                 method, dexItemFactory))
                     .setProto(proto));
+    eventConsumer.acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+        syntheticMethod, methodProcessingContext.getMethodContext());
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -145,7 +163,9 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowNoSuchMethodErrorMethod(
-      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      AppView<?> appView,
+      UtilityMethodsForCodeOptimizationsEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.noSuchMethodErrorType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
@@ -163,6 +183,8 @@
                     .setCode(
                         method -> getThrowNoSuchMethodErrorCodeTemplate(method, dexItemFactory))
                     .setProto(proto));
+    eventConsumer.acceptUtilityThrowNoSuchMethodErrorMethod(
+        syntheticMethod, methodProcessingContext.getMethodContext());
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -174,7 +196,9 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowRuntimeExceptionWithMessageMethod(
-      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      AppView<?> appView,
+      UtilityMethodsForCodeOptimizationsEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto =
         dexItemFactory.createProto(dexItemFactory.runtimeExceptionType, dexItemFactory.stringType);
@@ -194,6 +218,8 @@
                         method ->
                             getThrowRuntimeExceptionWithMessageCodeTemplate(method, dexItemFactory))
                     .setProto(proto));
+    eventConsumer.acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+        syntheticMethod, methodProcessingContext.getMethodContext());
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizationsEventConsumer.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizationsEventConsumer.java
new file mode 100644
index 0000000..90bad5f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizationsEventConsumer.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2023, 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;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface UtilityMethodsForCodeOptimizationsEventConsumer {
+
+  void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context);
+
+  void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+      ProgramMethod method, ProgramMethod context);
+
+  void acceptUtilityThrowIllegalAccessErrorMethod(ProgramMethod method, ProgramMethod context);
+
+  void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+      ProgramMethod method, ProgramMethod context);
+
+  void acceptUtilityThrowNoSuchMethodErrorMethod(ProgramMethod method, ProgramMethod context);
+
+  void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+      ProgramMethod method, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InstanceInitializerOutliner.java b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/ir/optimize/InstanceInitializerOutliner.java
rename to src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
index cbff832..cd94f57 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InstanceInitializerOutliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, 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;
+package com.android.tools.r8.ir.optimize.api;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.ir.synthetic.NewInstanceSourceCode;
@@ -58,7 +59,10 @@
   }
 
   public void rewriteInstanceInitializers(
-      IRCode code, ProgramMethod context, MethodProcessingContext methodProcessingContext) {
+      IRCode code,
+      ProgramMethod context,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
     // Do not outline from already synthesized methods.
     if (context.getDefinition().isD8R8Synthesized()) {
       return;
@@ -96,7 +100,10 @@
       }
       DexEncodedMethod synthesizedInstanceInitializer =
           createSynthesizedInstanceInitializer(
-              invokeDirect.getInvokedMethod(), apiReferenceLevel, methodProcessingContext);
+              invokeDirect.getInvokedMethod(),
+              apiReferenceLevel,
+              methodProcessor,
+              methodProcessingContext);
       List<Value> arguments = instruction.inValues();
       InvokeStatic outlinedMethodInvoke =
           InvokeStatic.builder()
@@ -134,7 +141,7 @@
       assert classApiLevel.isKnownApiLevel();
       DexEncodedMethod synthesizedNewInstance =
           createSynthesizedNewInstance(
-              newInstance.getType(), classApiLevel, methodProcessingContext);
+              newInstance.getType(), classApiLevel, methodProcessor, methodProcessingContext);
       InvokeStatic outlinedStaticInit =
           InvokeStatic.builder()
               .setMethod(synthesizedNewInstance.getReference())
@@ -174,6 +181,7 @@
   private DexEncodedMethod createSynthesizedNewInstance(
       DexType targetType,
       ComputedApiLevel computedApiLevel,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     DexProto proto = appView.dexItemFactory().createProto(factory.voidType);
     ProgramMethod method =
@@ -193,6 +201,9 @@
                             m ->
                                 NewInstanceSourceCode.create(appView, m.getHolderType(), targetType)
                                     .generateCfCode()));
+    methodProcessor
+        .getEventConsumer()
+        .acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext());
     synchronized (synthesizedMethods) {
       synthesizedMethods.add(method);
     }
@@ -202,6 +213,7 @@
   private DexEncodedMethod createSynthesizedInstanceInitializer(
       DexMethod targetMethod,
       ComputedApiLevel computedApiLevel,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     DexProto proto =
         appView
@@ -226,7 +238,9 @@
                                     .setConstructorTargetWithNewInstance(targetMethod)
                                     .setStaticSource(m)
                                     .build()));
-
+    methodProcessor
+        .getEventConsumer()
+        .acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext());
     synchronized (synthesizedMethods) {
       synthesizedMethods.add(method);
       ClassTypeElement exactType =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutlinerEventConsumer.java b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutlinerEventConsumer.java
new file mode 100644
index 0000000..7bd1b0e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutlinerEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.api;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface InstanceInitializerOutlinerEventConsumer {
+
+  void acceptInstanceInitializerOutline(ProgramMethod method, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerMethodProcessorEventConsumer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerMethodProcessorEventConsumer.java
new file mode 100644
index 0000000..e24fcd3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerMethodProcessorEventConsumer.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.enums;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnumUnboxerMethodProcessorEventConsumer {
+
+  void acceptEnumUnboxerCheckNotZeroContext(ProgramMethod method, ProgramMethod context);
+
+  void acceptEnumUnboxerLocalUtilityClassMethodContext(ProgramMethod method, ProgramMethod context);
+
+  void acceptEnumUnboxerSharedUtilityClassMethodContext(
+      ProgramMethod method, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index d0db72d..c9f6a96 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -123,6 +123,7 @@
     }
     assert code.isConsistentSSABeforeTypesAreCorrect(appView);
     ProgramMethod context = code.context();
+    EnumUnboxerMethodProcessorEventConsumer eventConsumer = methodProcessor.getEventConsumer();
     Map<Instruction, DexType> convertedEnums = createInitialConvertedEnums(code, prototypeChanges);
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     BasicBlockIterator blocks = code.listIterator();
@@ -186,19 +187,25 @@
             if (invokedMethod == factory.enumMembers.ordinalMethod
                 || invokedMethod.match(factory.enumMembers.hashCode)) {
               replaceEnumInvoke(
-                  iterator, invoke, getSharedUtilityClass().ensureOrdinalMethod(appView));
+                  iterator,
+                  invoke,
+                  getSharedUtilityClass().ensureOrdinalMethod(appView, context, eventConsumer));
               continue;
             } else if (invokedMethod.match(factory.enumMembers.equals)) {
               replaceEnumInvoke(
-                  iterator, invoke, getSharedUtilityClass().ensureEqualsMethod(appView));
+                  iterator,
+                  invoke,
+                  getSharedUtilityClass().ensureEqualsMethod(appView, context, eventConsumer));
               continue;
             } else if (invokedMethod == factory.enumMembers.compareTo
                 || invokedMethod == factory.enumMembers.compareToWithObject) {
               replaceEnumInvoke(
-                  iterator, invoke, getSharedUtilityClass().ensureCompareToMethod(appView));
+                  iterator,
+                  invoke,
+                  getSharedUtilityClass().ensureCompareToMethod(appView, context, eventConsumer));
               continue;
             } else if (invokedMethod == factory.enumMembers.nameMethod) {
-              rewriteNameMethod(iterator, invoke, enumType, methodProcessor);
+              rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
               continue;
             } else if (invokedMethod.match(factory.enumMembers.toString)) {
               DexMethod lookupMethod = enumUnboxingLens.lookupMethod(invokedMethod);
@@ -206,11 +213,11 @@
               // class, which was moved, and the lens code rewriter will rewrite the invoke to
               // that method.
               if (invoke.isInvokeSuper() || lookupMethod == invokedMethod) {
-                rewriteNameMethod(iterator, invoke, enumType, methodProcessor);
+                rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
                 continue;
               }
             } else if (invokedMethod == factory.objectMembers.getClass) {
-              rewriteNullCheck(iterator, invoke);
+              rewriteNullCheck(iterator, invoke, context, eventConsumer);
               continue;
             }
           } else if (invokedMethod == factory.stringBuilderMethods.appendObject
@@ -221,7 +228,8 @@
             DexType enumArgType = getEnumClassTypeOrNull(enumArg, convertedEnums);
             if (enumArgType != null) {
               ProgramMethod stringValueOfMethod =
-                  getLocalUtilityClass(enumArgType).ensureStringValueOfMethod(appView);
+                  getLocalUtilityClass(enumArgType)
+                      .ensureStringValueOfMethod(appView, context, eventConsumer);
               InvokeStatic toStringInvoke =
                   InvokeStatic.builder()
                       .setMethod(stringValueOfMethod)
@@ -258,7 +266,7 @@
               convertedEnums,
               iterator,
               affectedPhis,
-              methodProcessor);
+              eventConsumer);
         }
         if (instruction.isStaticGet()) {
           StaticGet staticGet = instruction.asStaticGet();
@@ -283,7 +291,7 @@
             // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
             InvokeStatic invoke =
                 InvokeStatic.builder()
-                    .setMethod(getSharedUtilityClass().getValuesMethod())
+                    .setMethod(getSharedUtilityClass().getValuesMethod(context, eventConsumer))
                     .setFreshOutValue(appView, code)
                     .setSingleArgument(sizeValue)
                     .build();
@@ -312,7 +320,7 @@
           DexType holder = instanceGet.getField().holder;
           if (unboxedEnumsData.isUnboxedEnum(holder)) {
             ProgramMethod fieldMethod =
-                ensureInstanceFieldMethod(instanceGet.getField(), methodProcessor);
+                ensureInstanceFieldMethod(instanceGet.getField(), context, eventConsumer);
             Value rewrittenOutValue =
                 code.createValue(
                     TypeElement.fromDexType(fieldMethod.getReturnType(), maybeNull(), appView));
@@ -363,7 +371,7 @@
       Map<Instruction, DexType> convertedEnums,
       InstructionListIterator instructionIterator,
       Set<Phi> affectedPhis,
-      MethodProcessor methodProcessor) {
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
     if (singleTarget == null) {
       return;
@@ -381,7 +389,8 @@
         if (!unboxedEnumsData.isUnboxedEnum(enumType)) {
           return;
         }
-        ProgramMethod valueOfMethod = getLocalUtilityClass(enumType).ensureValueOfMethod(appView);
+        ProgramMethod valueOfMethod =
+            getLocalUtilityClass(enumType).ensureValueOfMethod(appView, context, eventConsumer);
         Value outValue = invoke.outValue();
         Value rewrittenOutValue = null;
         if (outValue != null) {
@@ -406,7 +415,7 @@
         Value argument = invoke.getFirstArgument();
         DexType enumType = getEnumClassTypeOrNull(argument, convertedEnums);
         if (enumType != null) {
-          rewriteNullCheck(instructionIterator, invoke);
+          rewriteNullCheck(instructionIterator, invoke, context, eventConsumer);
         }
       } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
         assert invoke.arguments().size() == 2;
@@ -416,7 +425,8 @@
           replaceEnumInvoke(
               instructionIterator,
               invoke,
-              getSharedUtilityClass().ensureCheckNotZeroWithMessageMethod(appView));
+              getSharedUtilityClass()
+                  .ensureCheckNotZeroWithMessageMethod(appView, context, eventConsumer));
         }
       }
       return;
@@ -430,7 +440,8 @@
         DexType enumType = getEnumClassTypeOrNull(argument, convertedEnums);
         if (enumType != null) {
           ProgramMethod stringValueOfMethod =
-              getLocalUtilityClass(enumType).ensureStringValueOfMethod(appView);
+              getLocalUtilityClass(enumType)
+                  .ensureStringValueOfMethod(appView, context, eventConsumer);
           instructionIterator.replaceCurrentInstruction(
               new InvokeStatic(
                   stringValueOfMethod.getReference(), invoke.outValue(), invoke.arguments()));
@@ -481,6 +492,7 @@
                     .build();
             instructionIterator.replaceCurrentInstruction(replacement);
             convertedEnums.put(replacement, enumType);
+            eventConsumer.acceptEnumUnboxerCheckNotZeroContext(checkNotZeroMethod, context);
           }
         } else {
           assert false;
@@ -491,9 +503,16 @@
     }
   }
 
-  public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) {
+  public void rewriteNullCheck(
+      InstructionListIterator iterator,
+      InvokeMethod invoke,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
     assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
-    replaceEnumInvoke(iterator, invoke, getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
+    replaceEnumInvoke(
+        iterator,
+        invoke,
+        getSharedUtilityClass().ensureCheckNotZeroMethod(appView, context, eventConsumer));
   }
 
   private void removeRedundantValuesArrayCloning(
@@ -520,10 +539,12 @@
       InstructionListIterator iterator,
       InvokeMethodWithReceiver invoke,
       DexType enumType,
-      MethodProcessor methodProcessor) {
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
     ProgramMethod toStringMethod =
         getLocalUtilityClass(enumType)
-            .ensureGetInstanceFieldMethod(appView, factory.enumMembers.nameField);
+            .ensureGetInstanceFieldMethod(
+                appView, factory.enumMembers.nameField, context, eventConsumer);
     iterator.replaceCurrentInstruction(
         new InvokeStatic(toStringMethod.getReference(), invoke.outValue(), invoke.arguments()));
   }
@@ -553,13 +574,17 @@
     return iterator.insertConstIntInstruction(code, options, 0);
   }
 
-  private ProgramMethod ensureInstanceFieldMethod(DexField field, MethodProcessor methodProcessor) {
+  private ProgramMethod ensureInstanceFieldMethod(
+      DexField field,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
     EnumInstanceFieldKnownData enumFieldKnownData =
         unboxedEnumsData.getInstanceFieldData(field.holder, field);
     if (enumFieldKnownData.isOrdinal()) {
-      return getSharedUtilityClass().ensureOrdinalMethod(appView);
+      return getSharedUtilityClass().ensureOrdinalMethod(appView, context, eventConsumer);
     }
-    return getLocalUtilityClass(field.getHolderType()).ensureGetInstanceFieldMethod(appView, field);
+    return getLocalUtilityClass(field.getHolderType())
+        .ensureGetInstanceFieldMethod(appView, field, context, eventConsumer);
   }
 
   private void replaceEnumInvoke(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 77100b5..a186580 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
 import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
@@ -148,8 +149,9 @@
       IRConverter converter, ExecutorService executorService) throws ExecutionException {
     BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = HashBiMap.create();
     ProcessorContext processorContext = appView.createProcessorContext();
+    MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
     OneTimeMethodProcessor.Builder methodProcessorBuilder =
-        OneTimeMethodProcessor.builder(processorContext);
+        OneTimeMethodProcessor.builder(eventConsumer, processorContext);
 
     // Only duplicate checkNotNull() methods that are required for enum unboxing.
     checkNotNullMethods.removeIf(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
index 8dd1398..fc83f40 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -95,8 +96,9 @@
       fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
 
       // Create and process the utility methods.
+      MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
       OneTimeMethodProcessor.Builder methodProcessorBuilder =
-          OneTimeMethodProcessor.builder(appView.createProcessorContext());
+          OneTimeMethodProcessor.builder(eventConsumer, appView.createProcessorContext());
       utilityClasses.forEach(
           utilityClass -> {
             utilityClass.ensureMethods(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
index 6741924..4e7e556 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -59,7 +59,16 @@
 
   public ProgramMethod ensureGetInstanceFieldMethod(
       AppView<AppInfoWithLiveness> appView,
-      DexField field) {
+      DexField field,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureGetInstanceFieldMethod(appView, field);
+    eventConsumer.acceptEnumUnboxerLocalUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureGetInstanceFieldMethod(
+      AppView<AppInfoWithLiveness> appView, DexField field) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     String fieldName = field.getName().toString();
     DexString methodName;
@@ -82,7 +91,16 @@
                 .generateCfCode());
   }
 
-  public ProgramMethod ensureStringValueOfMethod(AppView<AppInfoWithLiveness> appView) {
+  public ProgramMethod ensureStringValueOfMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureStringValueOfMethod(appView);
+    eventConsumer.acceptEnumUnboxerLocalUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureStringValueOfMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     AbstractValue defaultValue =
         appView.abstractValueFactory().createSingleStringValue(dexItemFactory.createString("null"));
@@ -96,7 +114,16 @@
                 .generateCfCode());
   }
 
-  public ProgramMethod ensureValueOfMethod(AppView<AppInfoWithLiveness> appView) {
+  public ProgramMethod ensureValueOfMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureValueOfMethod(appView);
+    eventConsumer.acceptEnumUnboxerLocalUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureValueOfMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return internalEnsureMethod(
         appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index c24a972..fb0c4d8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -75,7 +75,16 @@
     ensureOrdinalMethod(appView);
   }
 
-  public ProgramMethod ensureCheckNotZeroMethod(AppView<AppInfoWithLiveness> appView) {
+  public ProgramMethod ensureCheckNotZeroMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureCheckNotZeroMethod(appView);
+    eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureCheckNotZeroMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return internalEnsureMethod(
         appView,
@@ -84,7 +93,16 @@
         method -> EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(dexItemFactory, method));
   }
 
-  public ProgramMethod ensureCheckNotZeroWithMessageMethod(AppView<AppInfoWithLiveness> appView) {
+  public ProgramMethod ensureCheckNotZeroWithMessageMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureCheckNotZeroWithMessageMethod(appView);
+    eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureCheckNotZeroWithMessageMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return internalEnsureMethod(
         appView,
@@ -95,7 +113,16 @@
             EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(dexItemFactory, method));
   }
 
-  public ProgramMethod ensureCompareToMethod(AppView<AppInfoWithLiveness> appView) {
+  public ProgramMethod ensureCompareToMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureCompareToMethod(appView);
+    eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureCompareToMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return internalEnsureMethod(
         appView,
@@ -105,7 +132,16 @@
         method -> EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(dexItemFactory, method));
   }
 
-  public ProgramMethod ensureEqualsMethod(AppView<AppInfoWithLiveness> appView) {
+  public ProgramMethod ensureEqualsMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureEqualsMethod(appView);
+    eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureEqualsMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return internalEnsureMethod(
         appView,
@@ -115,7 +151,16 @@
         method -> EnumUnboxingCfMethods.EnumUnboxingMethods_equals(dexItemFactory, method));
   }
 
-  public ProgramMethod ensureOrdinalMethod(AppView<AppInfoWithLiveness> appView) {
+  public ProgramMethod ensureOrdinalMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureOrdinalMethod(appView);
+    eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureOrdinalMethod(AppView<AppInfoWithLiveness> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return internalEnsureMethod(
         appView,
@@ -156,7 +201,9 @@
     return sharedUtilityClass;
   }
 
-  public ProgramMethod getValuesMethod() {
+  public ProgramMethod getValuesMethod(
+      ProgramMethod context, EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(valuesMethod, context);
     return valuesMethod;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 05f697c..b00db83 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -154,9 +154,7 @@
 
         LibraryMethodModelCollection.State optimizationState =
             optimizationStates.computeIfAbsent(
-                optimizer,
-                libraryMethodModelCollection ->
-                    libraryMethodModelCollection.createInitialState(methodProcessor));
+                optimizer, LibraryMethodModelCollection::createInitialState);
         optimizer.optimize(
             code,
             blockIterator,
@@ -166,6 +164,7 @@
             affectedValues,
             blocksToRemove,
             optimizationState,
+            methodProcessor,
             methodProcessingContext);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
index dbc5b0b..fef1563 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -20,7 +20,7 @@
 /** Used to model the behavior of library methods for optimization purposes. */
 public interface LibraryMethodModelCollection<T extends State> {
 
-  default T createInitialState(MethodProcessor methodProcessor) {
+  default T createInitialState() {
     return null;
   }
 
@@ -43,6 +43,7 @@
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToRemove,
       T state,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext);
 
   @SuppressWarnings("unchecked")
@@ -55,6 +56,7 @@
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToRemove,
       Object state,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     optimize(
         code,
@@ -65,6 +67,7 @@
         affectedValues,
         blocksToRemove,
         (T) state,
+        methodProcessor,
         methodProcessingContext);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
index 520c27a..9cfdcbd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
@@ -20,7 +20,7 @@
     implements LibraryMethodModelCollection<State> {
 
   @Override
-  public final State createInitialState(MethodProcessor methodProcessor) {
+  public final State createInitialState() {
     return null;
   }
 
@@ -43,6 +43,7 @@
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToRemove,
       State state,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     assert state == null;
     optimize(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index 835baa6..456ecc7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -58,8 +58,8 @@
   }
 
   @Override
-  public State createInitialState(MethodProcessor methodProcessor) {
-    return new State(methodProcessor);
+  public State createInitialState() {
+    return new State();
   }
 
   @Override
@@ -77,6 +77,7 @@
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToRemove,
       State state,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     if (invoke.isInvokeMethodWithReceiver()) {
       InvokeMethodWithReceiver invokeWithReceiver = invoke.asInvokeMethodWithReceiver();
@@ -86,6 +87,7 @@
             invokeWithReceiver,
             singleTarget,
             state,
+            methodProcessor,
             methodProcessingContext);
       } else if (singleTarget.getReference() == dexItemFactory.stringBuilderMethods.toString) {
         optimizeToString(instructionIterator, invokeWithReceiver);
@@ -98,6 +100,7 @@
       InvokeMethodWithReceiver invoke,
       DexClassAndMethod singleTarget,
       State state,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     boolean isStringBuilderUnused = state.isUnusedBuilder(invoke.getReceiver());
     if (invoke.hasOutValue() && (options.isGeneratingDex() || isStringBuilderUnused)) {
@@ -107,7 +110,7 @@
     }
     if (isStringBuilderUnused) {
       optimizeAppendOnUnusedStringBuilder(
-          instructionIterator, invoke, singleTarget, state, methodProcessingContext);
+          instructionIterator, invoke, singleTarget, methodProcessor, methodProcessingContext);
     }
   }
 
@@ -115,7 +118,7 @@
       InstructionListIterator instructionIterator,
       InvokeMethodWithReceiver invoke,
       DexClassAndMethod singleTarget,
-      State state,
+      MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     assert !invoke.hasOutValue();
     DexMethod appendMethod = singleTarget.getReference();
@@ -144,8 +147,8 @@
         // Replace the instruction by toStringIfNotNull().
         UtilityMethodForCodeOptimizations toStringIfNotNullMethod =
             UtilityMethodsForCodeOptimizations.synthesizeToStringIfNotNullMethod(
-                appView, methodProcessingContext);
-        toStringIfNotNullMethod.optimize(state.methodProcessor);
+                appView, methodProcessor.getEventConsumer(), methodProcessingContext);
+        toStringIfNotNullMethod.optimize(methodProcessor);
         InvokeStatic replacement =
             InvokeStatic.builder()
                 .setMethod(toStringIfNotNullMethod.getMethod())
@@ -168,14 +171,8 @@
 
   class State implements LibraryMethodModelCollection.State {
 
-    final MethodProcessor methodProcessor;
-
     final Reference2BooleanMap<Value> unusedBuilders = new Reference2BooleanOpenHashMap<>();
 
-    State(MethodProcessor methodProcessor) {
-      this.methodProcessor = methodProcessor;
-    }
-
     boolean isUnusedBuilder(Value value) {
       if (!unusedBuilders.containsKey(value)) {
         computeIsUnusedBuilder(value);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
index 713322d..a3782b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.optimize.OutlinerImpl.Outline;
+import com.android.tools.r8.ir.optimize.outliner.OutlinerImpl.Outline;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineOptimizationEventConsumer.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineOptimizationEventConsumer.java
new file mode 100644
index 0000000..4d8a9ed
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineOptimizationEventConsumer.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, 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.outliner;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ConcreteArtProfileCollectionAdditions;
+import java.util.Collection;
+
+public interface OutlineOptimizationEventConsumer {
+
+  void acceptOutlineMethod(ProgramMethod method, Collection<ProgramMethod> contexts);
+
+  static OutlineOptimizationEventConsumer create(
+      ArtProfileCollectionAdditions collectionAdditions) {
+    if (collectionAdditions.isNop()) {
+      return empty();
+    }
+    return create(collectionAdditions.asConcrete());
+  }
+
+  static OutlineOptimizationEventConsumer create(
+      ConcreteArtProfileCollectionAdditions collectionAdditions) {
+    return (method, contexts) -> {
+      for (ProgramMethod context : contexts) {
+        collectionAdditions.applyIfContextIsInProfile(
+            context,
+            additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+      }
+    };
+  }
+
+  static EmptyOutlineOptimizationEventConsumer empty() {
+    return EmptyOutlineOptimizationEventConsumer.getInstance();
+  }
+
+  class EmptyOutlineOptimizationEventConsumer implements OutlineOptimizationEventConsumer {
+
+    private static final EmptyOutlineOptimizationEventConsumer INSTANCE =
+        new EmptyOutlineOptimizationEventConsumer();
+
+    private EmptyOutlineOptimizationEventConsumer() {}
+
+    static EmptyOutlineOptimizationEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptOutlineMethod(ProgramMethod method, Collection<ProgramMethod> contexts) {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java
index b3ed5f5..f8d0177 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.optimize.OutlinerImpl;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
rename to src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
index 4a99fb4..7382a1e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, 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;
+package com.android.tools.r8.ir.optimize.outliner;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
@@ -60,13 +60,15 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.SourceCode;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
-import com.android.tools.r8.ir.optimize.outliner.OutlineCollection;
-import com.android.tools.r8.ir.optimize.outliner.Outliner;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
@@ -131,7 +133,7 @@
   /** Result of third step (see {@link OutlinerImpl#buildOutlineMethods()}. */
   private final Map<Outline, DexMethod> generatedOutlines = new HashMap<>();
 
-  static final int MAX_IN_SIZE = 5;  // Avoid using ranged calls for outlined code.
+  static final int MAX_IN_SIZE = 5; // Avoid using ranged calls for outlined code.
 
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory dexItemFactory;
@@ -223,17 +225,14 @@
 
     private final NumericType numericType;
 
-    private BinOpOutlineInstruction(
-        OutlineInstructionType type,
-        NumericType numericType) {
+    private BinOpOutlineInstruction(OutlineInstructionType type, NumericType numericType) {
       super(type);
       this.numericType = numericType;
     }
 
     static BinOpOutlineInstruction fromInstruction(Binop instruction) {
       return new BinOpOutlineInstruction(
-          OutlineInstructionType.fromInstruction(instruction),
-          instruction.getNumericType());
+          OutlineInstructionType.fromInstruction(instruction), instruction.getNumericType());
     }
 
     @Override
@@ -406,11 +405,7 @@
     private final boolean hasReceiver;
 
     private InvokeOutlineInstruction(
-        DexMethod method,
-        Type type,
-        boolean hasOutValue,
-        ValueType[] inputTypes,
-        DexProto proto) {
+        DexMethod method, Type type, boolean hasOutValue, ValueType[] inputTypes, DexProto proto) {
       super(OutlineInstructionType.INVOKE);
       hasReceiver = inputTypes.length != method.proto.parameters.values.length;
       assert !hasReceiver || inputTypes[0].isObject();
@@ -568,7 +563,7 @@
     final List<DexType> argumentTypes;
     final List<Integer> argumentMap;
     final List<OutlineInstruction> templateInstructions = new ArrayList<>();
-    final public DexType returnType;
+    public final DexType returnType;
 
     private DexProto proto;
 
@@ -765,7 +760,7 @@
   // This is the superclass for both collection candidates and actually replacing code.
   // TODO(sgjesse): Collect more information in the candidate collection and reuse that for
   // replacing.
-  abstract private class OutlineSpotter {
+  private abstract class OutlineSpotter {
 
     final ProgramMethod method;
     final IRCode irCode;
@@ -955,7 +950,7 @@
     }
 
     private DexType argumentTypeFromInvoke(InvokeMethod invoke, int index) {
-      boolean withReceiver =  invoke.isInvokeMethodWithReceiver() || invoke.isInvokePolymorphic();
+      boolean withReceiver = invoke.isInvokeMethodWithReceiver() || invoke.isInvokePolymorphic();
       if (withReceiver && index == 0) {
         return invoke.getInvokedMethod().holder;
       }
@@ -1355,8 +1350,14 @@
             identifyOutlineSites(code);
           },
           executorService);
-      List<ProgramMethod> outlineMethods = buildOutlineMethods();
-      converter.optimizeSynthesizedMethods(outlineMethods, executorService);
+      ArtProfileCollectionAdditions artProfileCollectionAdditions =
+          ArtProfileCollectionAdditions.create(appView);
+      List<ProgramMethod> outlineMethods =
+          buildOutlineMethods(
+              OutlineOptimizationEventConsumer.create(artProfileCollectionAdditions));
+      artProfileCollectionAdditions.commit(appView);
+      MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
+      converter.optimizeSynthesizedMethods(outlineMethods, eventConsumer, executorService);
       feedback.updateVisibleOptimizationInfo();
       forEachSelectedOutliningMethod(
           converter,
@@ -1503,7 +1504,7 @@
     return result;
   }
 
-  public List<ProgramMethod> buildOutlineMethods() {
+  public List<ProgramMethod> buildOutlineMethods(OutlineOptimizationEventConsumer eventConsumer) {
     ProcessorContext outlineProcessorContext = appView.createProcessorContext();
     Map<DexMethod, MethodProcessingContext> methodProcessingContexts = new IdentityHashMap<>();
     List<ProgramMethod> outlineMethods = new ArrayList<>();
@@ -1544,6 +1545,7 @@
                           representative.getDefinition().getClassFileVersion());
                     }
                   });
+      eventConsumer.acceptOutlineMethod(outlineMethod, sites);
       generatedOutlines.put(outline, outlineMethod.getReference());
       outlineMethods.add(outlineMethod);
     }
@@ -1660,12 +1662,10 @@
     }
 
     @Override
-    public void setUp() {
-    }
+    public void setUp() {}
 
     @Override
-    public void clear() {
-    }
+    public void clear() {}
 
     @Override
     public void buildPrelude(IRBuilder builder) {
@@ -1715,14 +1715,14 @@
     }
 
     @Override
-    public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset,
-        IRBuilder builder) {
+    public void resolveAndBuildSwitch(
+        int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
       throw new Unreachable("Unexpected call to resolveAndBuildSwitch");
     }
 
     @Override
-    public void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset,
-        IRBuilder builder) {
+    public void resolveAndBuildNewArrayFilledData(
+        int arrayRef, int payloadOffset, IRBuilder builder) {
       throw new Unreachable("Unexpected call to resolveAndBuildNewArrayFilledData");
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
index 3b93c50..36d883a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
@@ -36,11 +36,20 @@
   private int stackHeight = 0;
   private Instruction insertPosition = null;
 
+  // Set a threshold for the number of loads associated with a stored value that we attempt to move
+  // up. For big arrays with straight control flow moving loads up will have bad performance due
+  // to using an iterator to move up and down.
+  private static final int LOAD_USER_THRESHOLD = 20;
+
   private final Point firstLoad =
       new Point(
           (i) -> {
             if (PeepholeHelper.withoutLocalInfo(Instruction::isLoad).test(i)) {
-              local = i.asLoad().src();
+              Load load = i.asLoad();
+              if (load.getFirstOperand().numberOfAllUsers() > LOAD_USER_THRESHOLD) {
+                return false;
+              }
+              local = load.src();
               return true;
             }
             return false;
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
index 138e738..5899d87 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -20,6 +20,8 @@
 
     public abstract Builder addMemberEntry(MemberNaming entry);
 
+    public abstract MemberNaming lookupMemberEntry(Signature signature);
+
     public abstract ClassNaming build();
 
     /** This is an optional method, may be implemented as no-op */
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index cbfb5e3..afc7132 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -80,6 +80,11 @@
     }
 
     @Override
+    public MemberNaming lookupMemberEntry(Signature signature) {
+      return null;
+    }
+
+    @Override
     public ClassNamingForMapApplier build() {
       return new ClassNamingForMapApplier(
           renamedName, originalName, position, qualifiedMethodMembers, methodMembers, fieldMembers);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 98a08a1..15efe36 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -76,6 +76,13 @@
     }
 
     @Override
+    public MemberNaming lookupMemberEntry(Signature signature) {
+      return signature.isFieldSignature()
+          ? fieldMembers.get(signature.asFieldSignature())
+          : methodMembers.get(signature.asMethodSignature());
+    }
+
+    @Override
     public ClassNamingForNameMapper build() {
       Map<String, MappedRangesOfName> map;
 
@@ -350,11 +357,11 @@
   @Override
   public MemberNaming lookup(Signature renamedSignature) {
     if (renamedSignature.kind() == SignatureKind.METHOD) {
-      assert renamedSignature instanceof MethodSignature;
+      assert renamedSignature.isMethodSignature();
       return methodMembers.get(renamedSignature);
     } else {
       assert renamedSignature.kind() == SignatureKind.FIELD;
-      assert renamedSignature instanceof FieldSignature;
+      assert renamedSignature.isFieldSignature();
       return fieldMembers.get(renamedSignature);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 318034f..a26d531 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Iterables;
@@ -142,11 +143,11 @@
         additionalMappingInformation, info, onProhibitedAddition);
   }
 
-  void addAllMappingInformationInternal(List<ReferentialMappingInformation> otherInfo) {
-    if (additionalMappingInformation == EMPTY_MAPPING_INFORMATION) {
-      additionalMappingInformation = new ArrayList<>();
+  public void addAllMappingInformation(List<ReferentialMappingInformation> infos) {
+    Consumer<MappingInformation> emptyConsumer = ConsumerUtils.emptyConsumer();
+    for (ReferentialMappingInformation mappingInformation : infos) {
+      addMappingInformation(mappingInformation, emptyConsumer);
     }
-    additionalMappingInformation.addAll(otherInfo);
   }
 
   public boolean isCompilerSynthesized() {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 14a6a22..17157c3 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -538,14 +538,15 @@
         || lastAddedNaming == null
         || !lastAddedNaming.getRenamedName().equals(renamedName)
         || !lastAddedNaming.getOriginalSignature().equals(originalSignature)) {
-      MemberNaming newMemberNaming =
-          new MemberNaming(
-              originalSignature,
-              getResidualSignatureForMemberNaming(
-                  residualSignature, originalSignature, renamedName),
-              new LinePosition(lineNumber));
+      Signature lookupKey =
+          getResidualSignatureForMemberNaming(residualSignature, originalSignature, renamedName);
+      MemberNaming newMemberNaming = classNamingBuilder.lookupMemberEntry(lookupKey);
+      if (newMemberNaming == null) {
+        newMemberNaming =
+            new MemberNaming(originalSignature, lookupKey, new LinePosition(lineNumber));
+      }
       if (additionalMappingInformation.isSet()) {
-        newMemberNaming.addAllMappingInformationInternal(additionalMappingInformation.get());
+        newMemberNaming.addAllMappingInformation(additionalMappingInformation.get());
       }
       classNamingBuilder.addMemberEntry(newMemberNaming);
       residualSignature.clear();
@@ -553,7 +554,7 @@
       return newMemberNaming;
     }
     if (additionalMappingInformation.isSet()) {
-      lastAddedNaming.addAllMappingInformationInternal(additionalMappingInformation.get());
+      lastAddedNaming.addAllMappingInformation(additionalMappingInformation.get());
       additionalMappingInformation.clear();
     }
     residualSignature.clear();
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
index 48baf2f..ee7b46e 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
@@ -51,7 +51,7 @@
 
   @Override
   public boolean allowOther(MappingInformation information) {
-    return true;
+    return !information.isCompilerSynthesizedMappingInformation();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index bf1afad..16edb6a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder;
 import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepMethodInfo;
@@ -193,18 +194,23 @@
       Timing timing)
       throws ExecutionException {
     timing.begin("Optimize components");
+    ArtProfileCollectionAdditions artProfileCollectionAdditions =
+        ArtProfileCollectionAdditions.create(appView);
+    ArgumentPropagatorSyntheticEventConsumer eventConsumer =
+        ArgumentPropagatorSyntheticEventConsumer.create(artProfileCollectionAdditions);
     ProcessorContext processorContext = appView.createProcessorContext();
     Collection<Builder> partialGraphLensBuilders =
         ThreadUtils.processItemsWithResults(
             stronglyConnectedProgramComponents,
             classes ->
-                new StronglyConnectedComponentOptimizer(processorContext)
+                new StronglyConnectedComponentOptimizer(eventConsumer, processorContext)
                     .optimize(
                         classes,
                         interfaceDispatchOutsideProgram.getOrDefault(
                             classes, DexMethodSignatureSet.empty()),
                         affectedClassConsumer),
             executorService);
+    artProfileCollectionAdditions.commit(appView);
     timing.end();
 
     // Merge all the partial, disjoint graph lens builders into a single graph lens.
@@ -267,9 +273,12 @@
     private final Map<DexMethodSignature, Pair<AllowedPrototypeChanges, DexMethodSignature>>
         occupiedMethodSignatures = new HashMap<>();
 
+    private final ArgumentPropagatorSyntheticEventConsumer eventConsumer;
     private final ProcessorContext processorContext;
 
-    public StronglyConnectedComponentOptimizer(ProcessorContext processorContext) {
+    public StronglyConnectedComponentOptimizer(
+        ArgumentPropagatorSyntheticEventConsumer eventConsumer, ProcessorContext processorContext) {
+      this.eventConsumer = eventConsumer;
       this.processorContext = processorContext;
     }
 
@@ -876,16 +885,17 @@
           }
         }
       }
-      DexType extraArgumentType =
+      DexProgramClass extraArgumentClass =
           appView
               .getSyntheticItems()
               .createClass(
                   kinds -> kinds.NON_FIXED_INIT_TYPE_ARGUMENT,
                   processorContext.createMethodProcessingContext(method).createUniqueContext(),
-                  appView)
-              .getType();
+                  appView);
+      eventConsumer.acceptInitializerArgumentClass(extraArgumentClass, method);
       RewrittenPrototypeDescription finalPrototypeChanges =
-          prototypeChanges.withExtraParameters(new ExtraUnusedNullParameter(extraArgumentType));
+          prototypeChanges.withExtraParameters(
+              new ExtraUnusedNullParameter(extraArgumentClass.getType()));
       boolean added =
           instanceInitializerSignatures.add(
               finalPrototypeChanges.rewriteMethod(method, dexItemFactory));
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorSyntheticEventConsumer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorSyntheticEventConsumer.java
new file mode 100644
index 0000000..aae0602
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorSyntheticEventConsumer.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2023, 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.optimize.argumentpropagation;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.profile.art.rewriting.ArtProfileCollectionAdditions;
+import com.android.tools.r8.profile.art.rewriting.ConcreteArtProfileCollectionAdditions;
+
+public interface ArgumentPropagatorSyntheticEventConsumer {
+
+  void acceptInitializerArgumentClass(DexProgramClass clazz, ProgramMethod context);
+
+  static ArgumentPropagatorSyntheticEventConsumer create(
+      ArtProfileCollectionAdditions collectionAdditions) {
+    if (collectionAdditions.isNop()) {
+      return empty();
+    }
+    return create(collectionAdditions.asConcrete());
+  }
+
+  static ArgumentPropagatorSyntheticEventConsumer create(
+      ConcreteArtProfileCollectionAdditions collectionAdditions) {
+    return (clazz, context) ->
+        collectionAdditions.applyIfContextIsInProfile(
+            context, additionsBuilder -> additionsBuilder.addRule(clazz));
+  }
+
+  static ArgumentPropagatorSyntheticEventConsumer empty() {
+    return EmptyArgumentPropagatorSyntheticEventConsumer.getInstance();
+  }
+
+  class EmptyArgumentPropagatorSyntheticEventConsumer
+      implements ArgumentPropagatorSyntheticEventConsumer {
+
+    private static EmptyArgumentPropagatorSyntheticEventConsumer INSTANCE =
+        new EmptyArgumentPropagatorSyntheticEventConsumer();
+
+    private EmptyArgumentPropagatorSyntheticEventConsumer() {}
+
+    static EmptyArgumentPropagatorSyntheticEventConsumer getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public void acceptInitializerArgumentClass(DexProgramClass clazz, ProgramMethod context) {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
index 2cdb3e0..e7012b8 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
@@ -4,11 +4,14 @@
 
 package com.android.tools.r8.profile.art;
 
+import com.android.tools.r8.graph.AppInfo;
 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.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -16,17 +19,15 @@
 
 public abstract class ArtProfileCollection {
 
-  public static ArtProfileCollection createInitialArtProfileCollection(InternalOptions options) {
+  public static ArtProfileCollection createInitialArtProfileCollection(
+      AppInfo appInfo, InternalOptions options) {
     ArtProfileOptions artProfileOptions = options.getArtProfileOptions();
     Collection<ArtProfileForRewriting> artProfilesForRewriting =
         artProfileOptions.getArtProfilesForRewriting();
-    if (artProfilesForRewriting.isEmpty()) {
-      return empty();
-    }
-    if (artProfileOptions.isPassthrough()) {
-      return passthrough();
-    }
-    List<ArtProfile> artProfiles = new ArrayList<>(artProfilesForRewriting.size());
+    List<ArtProfile> artProfiles =
+        new ArrayList<>(
+            artProfilesForRewriting.size()
+                + BooleanUtils.intValue(artProfileOptions.isCompletenessCheckForTestingEnabled()));
     for (ArtProfileForRewriting artProfileForRewriting :
         options.getArtProfileOptions().getArtProfilesForRewriting()) {
       ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
@@ -35,17 +36,31 @@
       artProfileForRewriting.getArtProfileProvider().getArtProfile(artProfileBuilder);
       artProfiles.add(artProfileBuilder.build());
     }
+    if (artProfileOptions.isCompletenessCheckForTestingEnabled()) {
+      artProfiles.add(createCompleteArtProfile(appInfo));
+    }
+    if (artProfiles.isEmpty()) {
+      return empty();
+    }
     return new NonEmptyArtProfileCollection(artProfiles);
   }
 
+  private static ArtProfile createCompleteArtProfile(AppInfo appInfo) {
+    ArtProfile.Builder artProfileBuilder = ArtProfile.builder();
+    for (DexProgramClass clazz : appInfo.classesWithDeterministicOrder()) {
+      artProfileBuilder.addRule(ArtProfileClassRule.builder().setType(clazz.getType()).build());
+      clazz.forEachMethod(
+          method ->
+              artProfileBuilder.addRule(
+                  ArtProfileMethodRule.builder().setMethod(method.getReference()).build()));
+    }
+    return artProfileBuilder.build();
+  }
+
   public static EmptyArtProfileCollection empty() {
     return EmptyArtProfileCollection.getInstance();
   }
 
-  public static PassthroughArtProfileCollection passthrough() {
-    return PassthroughArtProfileCollection.getInstance();
-  }
-
   public abstract boolean isNonEmpty();
 
   public abstract NonEmptyArtProfileCollection asNonEmpty();
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
new file mode 100644
index 0000000..21a7635
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCompletenessChecker.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2023, 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.profile.art;
+
+import static com.android.tools.r8.profile.art.ArtProfileCompletenessChecker.CompletenessExceptions.ALLOW_MISSING_ENUM_UNBOXING_UTILITY_METHODS;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ArtProfileCompletenessChecker {
+
+  public enum CompletenessExceptions {
+    ALLOW_MISSING_ENUM_UNBOXING_UTILITY_METHODS
+  }
+
+  public static boolean verify(
+      AppView<?> appView, CompletenessExceptions... completenessExceptions) {
+    if (appView.options().getArtProfileOptions().isCompletenessCheckForTestingEnabled()) {
+      ArtProfile completeArtProfile = appView.getArtProfileCollection().asNonEmpty().getLast();
+      assert verifyProfileIsComplete(
+          appView, completeArtProfile, Sets.newHashSet(completenessExceptions));
+    }
+    return true;
+  }
+
+  private static boolean verifyProfileIsComplete(
+      AppView<?> appView,
+      ArtProfile artProfile,
+      Set<CompletenessExceptions> completenessExceptions) {
+    assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
+    List<DexReference> missing = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+      if (appView.horizontallyMergedClasses().hasBeenMergedIntoDifferentType(clazz.getType())
+          || (appView.hasVerticallyMergedClasses()
+              && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(clazz.getType()))
+          || appView.unboxedEnums().isUnboxedEnum(clazz)) {
+        continue;
+      }
+      if (!artProfile.containsClassRule(clazz.getType())) {
+        recordMissingDefinition(appView, clazz, completenessExceptions, missing);
+      }
+      for (ProgramMethod method : clazz.programMethods()) {
+        if (!artProfile.containsMethodRule(method.getReference())) {
+          recordMissingDefinition(appView, method, completenessExceptions, missing);
+        }
+      }
+    }
+    if (!missing.isEmpty()) {
+      String message =
+          StringUtils.join(System.lineSeparator(), missing, DexReference::toSmaliString);
+      assert false : message;
+    }
+    return true;
+  }
+
+  private static void recordMissingDefinition(
+      AppView<?> appView,
+      ProgramDefinition definition,
+      Set<CompletenessExceptions> completenessExceptions,
+      List<DexReference> missing) {
+    if (completenessExceptions.contains(ALLOW_MISSING_ENUM_UNBOXING_UTILITY_METHODS)) {
+      DexType contextType = definition.getContextType();
+      SyntheticItems syntheticItems = appView.getSyntheticItems();
+      if (syntheticItems.isSynthetic(contextType)) {
+        if (syntheticItems.isSyntheticOfKind(
+                contextType, naming -> naming.ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD)
+            || syntheticItems.isSyntheticOfKind(
+                contextType, naming -> naming.ENUM_UNBOXING_LOCAL_UTILITY_CLASS)
+            || syntheticItems.isSyntheticOfKind(
+                contextType, naming -> naming.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
+          return;
+        }
+      }
+    }
+    missing.add(definition.getReference());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
index 231a2d6..d7c3f12 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -4,13 +4,17 @@
 
 package com.android.tools.r8.profile.art;
 
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
+
 import java.util.Collection;
 import java.util.Collections;
 
 public class ArtProfileOptions {
 
   private Collection<ArtProfileForRewriting> artProfilesForRewriting = Collections.emptyList();
-  private boolean passthrough;
+  private boolean enableCompletenessCheckForTesting =
+      parseSystemPropertyOrDefault(
+          "com.android.tools.r8.artprofilerewritingcompletenesscheck", false);
 
   public ArtProfileOptions() {}
 
@@ -18,17 +22,25 @@
     return artProfilesForRewriting;
   }
 
+  public boolean isCompletenessCheckForTestingEnabled() {
+    return enableCompletenessCheckForTesting;
+  }
+
+  public boolean isIncludingApiReferenceStubs() {
+    // We only include API reference stubs in the residual ART profiles for completeness testing.
+    // This is because the API reference stubs should never be used at runtime except for
+    // verification, meaning there should be no need to AOT them.
+    return enableCompletenessCheckForTesting;
+  }
+
   public ArtProfileOptions setArtProfilesForRewriting(Collection<ArtProfileForRewriting> inputs) {
     this.artProfilesForRewriting = inputs;
     return this;
   }
 
-  public boolean isPassthrough() {
-    return passthrough;
-  }
-
-  public ArtProfileOptions setPassthrough(boolean passthrough) {
-    this.passthrough = passthrough;
+  public ArtProfileOptions setEnableCompletenessCheckForTesting(
+      boolean enableCompletenessCheckForTesting) {
+    this.enableCompletenessCheckForTesting = enableCompletenessCheckForTesting;
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
index b196906..d7f72ef 100644
--- a/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/NonEmptyArtProfileCollection.java
@@ -10,17 +10,19 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 import java.util.function.Function;
 
 public class NonEmptyArtProfileCollection extends ArtProfileCollection
     implements Iterable<ArtProfile> {
 
-  private final Collection<ArtProfile> artProfiles;
+  private final List<ArtProfile> artProfiles;
 
-  public NonEmptyArtProfileCollection(Collection<ArtProfile> artProfiles) {
+  public NonEmptyArtProfileCollection(List<ArtProfile> artProfiles) {
     this.artProfiles = artProfiles;
   }
 
@@ -34,6 +36,10 @@
     return this;
   }
 
+  public ArtProfile getLast() {
+    return ListUtils.last(artProfiles);
+  }
+
   @Override
   public Iterator<ArtProfile> iterator() {
     return artProfiles.iterator();
@@ -53,6 +59,14 @@
 
   @Override
   public void supplyConsumers(AppView<?> appView) {
+    if (appView.options().getArtProfileOptions().isCompletenessCheckForTestingEnabled()) {
+      assert ArtProfileCompletenessChecker.verify(appView);
+      ListUtils.removeLast(artProfiles);
+      if (artProfiles.isEmpty()) {
+        appView.setArtProfileCollection(ArtProfileCollection.empty());
+        return;
+      }
+    }
     NonEmptyArtProfileCollection collection =
         appView.getNamingLens().isIdentityLens()
             ? this
@@ -75,11 +89,10 @@
   }
 
   private NonEmptyArtProfileCollection map(Function<ArtProfile, ArtProfile> fn) {
-    ImmutableList.Builder<ArtProfile> newArtProfiles =
-        ImmutableList.builderWithExpectedSize(artProfiles.size());
+    List<ArtProfile> newArtProfiles = new ArrayList<>(artProfiles.size());
     for (ArtProfile artProfile : artProfiles) {
       newArtProfiles.add(fn.apply(artProfile));
     }
-    return new NonEmptyArtProfileCollection(newArtProfiles.build());
+    return new NonEmptyArtProfileCollection(newArtProfiles);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/PassthroughArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/PassthroughArtProfileCollection.java
deleted file mode 100644
index ac92f64..0000000
--- a/src/main/java/com/android/tools/r8/profile/art/PassthroughArtProfileCollection.java
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright (c) 2022, 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.profile.art;
-
-import com.android.tools.r8.TextInputStream;
-import com.android.tools.r8.TextOutputStream;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.MutableArtProfileClassRule;
-import com.android.tools.r8.profile.art.ArtProfileBuilderUtils.MutableArtProfileMethodRule;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.UncheckedIOException;
-import java.util.function.Consumer;
-
-public class PassthroughArtProfileCollection extends ArtProfileCollection {
-
-  private static final PassthroughArtProfileCollection INSTANCE =
-      new PassthroughArtProfileCollection();
-
-  private PassthroughArtProfileCollection() {}
-
-  static PassthroughArtProfileCollection getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public boolean isNonEmpty() {
-    return false;
-  }
-
-  @Override
-  public NonEmptyArtProfileCollection asNonEmpty() {
-    return null;
-  }
-
-  @Override
-  public ArtProfileCollection rewrittenWithLens(GraphLens lens) {
-    return this;
-  }
-
-  @Override
-  public ArtProfileCollection rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) {
-    return this;
-  }
-
-  @Override
-  public void supplyConsumers(AppView<?> appView) {
-    for (ArtProfileForRewriting artProfileForRewriting :
-        appView.options().getArtProfileOptions().getArtProfilesForRewriting()) {
-      ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
-      ArtProfileConsumer artProfileConsumer =
-          EmptyArtProfileConsumer.orEmpty(artProfileForRewriting.getResidualArtProfileConsumer());
-      supplyArtProfileConsumer(appView, artProfileConsumer, artProfileProvider);
-      artProfileConsumer.finished(appView.reporter());
-    }
-  }
-
-  private void supplyArtProfileConsumer(
-      AppView<?> appView,
-      ArtProfileConsumer artProfileConsumer,
-      ArtProfileProvider artProfileProvider) {
-    ArtProfileConsumerSupplier artProfileConsumerSupplier =
-        new ArtProfileConsumerSupplier(artProfileConsumer);
-    try {
-      ArtProfileRuleConsumer ruleConsumer =
-          EmptyArtProfileRuleConsumer.orEmpty(artProfileConsumer.getRuleConsumer());
-      artProfileProvider.getArtProfile(
-          new ArtProfileBuilder() {
-
-            @Override
-            public ArtProfileBuilder addClassRule(
-                Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer) {
-              MutableArtProfileClassRule classRule = new MutableArtProfileClassRule();
-              classRuleBuilderConsumer.accept(classRule);
-              ruleConsumer.acceptClassRule(
-                  classRule.getClassReference(), classRule.getClassRuleInfo());
-              artProfileConsumerSupplier.supply(classRule);
-              return this;
-            }
-
-            @Override
-            public ArtProfileBuilder addMethodRule(
-                Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer) {
-              MutableArtProfileMethodRule methodRule = new MutableArtProfileMethodRule();
-              methodRuleBuilderConsumer.accept(methodRule);
-              ruleConsumer.acceptMethodRule(
-                  methodRule.getMethodReference(), methodRule.getMethodRuleInfo());
-              artProfileConsumerSupplier.supply(methodRule);
-              return this;
-            }
-
-            @Override
-            public ArtProfileBuilder addHumanReadableArtProfile(
-                TextInputStream textInputStream,
-                Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
-              HumanReadableArtProfileParser.Builder parserBuilder =
-                  HumanReadableArtProfileParser.builder()
-                      .setReporter(appView.reporter())
-                      .setProfileBuilder(this);
-              parserBuilderConsumer.accept(parserBuilder);
-              HumanReadableArtProfileParser parser = parserBuilder.build();
-              parser.parse(textInputStream, artProfileProvider.getOrigin());
-              return this;
-            }
-          });
-    } finally {
-      artProfileConsumerSupplier.close();
-    }
-  }
-
-  @Override
-  public ArtProfileCollection withoutPrunedItems(PrunedItems prunedItems) {
-    return this;
-  }
-
-  private static class ArtProfileConsumerSupplier {
-
-    private final OutputStreamWriter outputStreamWriter;
-
-    ArtProfileConsumerSupplier(ArtProfileConsumer artProfileConsumer) {
-      TextOutputStream textOutputStream = artProfileConsumer.getHumanReadableArtProfileConsumer();
-      this.outputStreamWriter =
-          textOutputStream != null
-              ? new OutputStreamWriter(
-                  textOutputStream.getOutputStream(), textOutputStream.getCharset())
-              : null;
-      ;
-    }
-
-    void supply(MutableArtProfileClassRule classRule) {
-      if (outputStreamWriter != null) {
-        try {
-          classRule.writeHumanReadableRuleString(outputStreamWriter);
-          outputStreamWriter.write('\n');
-        } catch (IOException e) {
-          throw new UncheckedIOException(e);
-        }
-      }
-    }
-
-    void supply(MutableArtProfileMethodRule methodRule) {
-      if (outputStreamWriter != null) {
-        try {
-          methodRule.writeHumanReadableRuleString(outputStreamWriter);
-          outputStreamWriter.write('\n');
-        } catch (IOException e) {
-          throw new UncheckedIOException(e);
-        }
-      }
-    }
-
-    void close() {
-      if (outputStreamWriter != null) {
-        try {
-          outputStreamWriter.close();
-        } catch (IOException e) {
-          throw new UncheckedIOException(e);
-        }
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
index 988215e..b210019 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
@@ -12,6 +14,7 @@
 import com.android.tools.r8.profile.art.ArtProfile;
 import com.android.tools.r8.profile.art.ArtProfileClassRule;
 import com.android.tools.r8.profile.art.ArtProfileMethodRule;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
 import com.android.tools.r8.profile.art.ArtProfileRule;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -47,6 +50,12 @@
     this.artProfile = artProfile;
   }
 
+  void applyIfContextIsInProfile(DexType context, Consumer<ArtProfileAdditions> fn) {
+    if (artProfile.containsClassRule(context)) {
+      fn.accept(this);
+    }
+  }
+
   void applyIfContextIsInProfile(
       DexMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
     ArtProfileMethodRule contextMethodRule = artProfile.getMethodRule(context);
@@ -88,7 +97,12 @@
     }
   }
 
-  private void addClassRule(DexType type) {
+  public ArtProfileAdditions addClassRule(DexClass clazz) {
+    addClassRule(clazz.getType());
+    return this;
+  }
+
+  public void addClassRule(DexType type) {
     if (artProfile.containsClassRule(type)) {
       return;
     }
@@ -99,6 +113,21 @@
 
   private void addMethodRuleFromContext(
       DexMethod method, ArtProfileMethodRule contextMethodRule, MethodRuleAdditionConfig config) {
+    addMethodRule(
+        method,
+        methodRuleInfoBuilder ->
+            config.configureMethodRuleInfo(methodRuleInfoBuilder, contextMethodRule));
+  }
+
+  public ArtProfileAdditions addMethodRule(
+      DexClassAndMethod method,
+      Consumer<ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
+    return addMethodRule(method.getReference(), methodRuleInfoBuilderConsumer);
+  }
+
+  public ArtProfileAdditions addMethodRule(
+      DexMethod method,
+      Consumer<ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
     // Create profile rule for method.
     ArtProfileMethodRule.Builder methodRuleBuilder =
         methodRuleAdditions.computeIfAbsent(
@@ -106,10 +135,10 @@
 
     // Setup the rule.
     synchronized (methodRuleBuilder) {
-      methodRuleBuilder.acceptMethodRuleInfoBuilder(
-          methodRuleInfoBuilder ->
-              config.configureMethodRuleInfo(methodRuleInfoBuilder, contextMethodRule));
+      methodRuleBuilder.acceptMethodRuleInfoBuilder(methodRuleInfoBuilderConsumer);
     }
+
+    return this;
   }
 
   void removeMovedMethodRule(ProgramMethod oldMethod, ProgramMethod newMethod) {
@@ -160,8 +189,8 @@
 
   ArtProfileAdditions rewriteMethodReferences(Function<DexMethod, DexMethod> methodFn) {
     ArtProfileAdditions rewrittenAdditions = new ArtProfileAdditions(artProfile);
-    assert classRuleAdditions.isEmpty();
     assert methodRuleRemovals.isEmpty();
+    rewrittenAdditions.classRuleAdditions.putAll(classRuleAdditions);
     methodRuleAdditions.forEach(
         (method, methodRuleBuilder) -> {
           DexMethod newMethod = methodFn.apply(method);
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
index 858de9d..36d235f 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
@@ -43,7 +43,7 @@
     return false;
   }
 
-  ConcreteArtProfileCollectionAdditions asConcrete() {
+  public ConcreteArtProfileCollectionAdditions asConcrete() {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingApiReferenceStubberEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingApiReferenceStubberEventConsumer.java
new file mode 100644
index 0000000..683bb93
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingApiReferenceStubberEventConsumer.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2023, 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.profile.art.rewriting;
+
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.androidapi.ApiReferenceStubberEventConsumer;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexProgramClass;
+
+public class ArtProfileRewritingApiReferenceStubberEventConsumer
+    implements ApiReferenceStubberEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions collectionAdditions;
+  private final ApiReferenceStubberEventConsumer parent;
+
+  private ArtProfileRewritingApiReferenceStubberEventConsumer(
+      ConcreteArtProfileCollectionAdditions collectionAdditions,
+      ApiReferenceStubberEventConsumer parent) {
+    this.collectionAdditions = collectionAdditions;
+    this.parent = parent;
+  }
+
+  public static ArtProfileRewritingApiReferenceStubberEventConsumer attach(
+      ConcreteArtProfileCollectionAdditions collectionAdditions,
+      ApiReferenceStubberEventConsumer eventConsumer) {
+    return new ArtProfileRewritingApiReferenceStubberEventConsumer(
+        collectionAdditions, eventConsumer);
+  }
+
+  @Override
+  public void acceptMockedLibraryClass(DexProgramClass mockClass, DexLibraryClass libraryClass) {
+    parent.acceptMockedLibraryClass(mockClass, libraryClass);
+  }
+
+  @Override
+  public void acceptMockedLibraryClassContext(
+      DexProgramClass mockClass, DexLibraryClass libraryClass, DexProgramClass context) {
+    collectionAdditions.applyIfContextIsInProfile(
+        context,
+        additions ->
+            additions
+                .addClassRule(mockClass)
+                .addMethodRule(mockClass.getProgramClassInitializer(), emptyConsumer()));
+    parent.acceptMockedLibraryClassContext(mockClass, libraryClass, context);
+  }
+
+  @Override
+  public void finished(AppView<?> appView) {
+    collectionAdditions.commit(appView);
+    parent.finished(appView);
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.java
new file mode 100644
index 0000000..3cc4631
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2023, 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.profile.art.rewriting;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+import java.util.Set;
+
+public class ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer
+    extends CfClassSynthesizerDesugaringEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final CfClassSynthesizerDesugaringEventConsumer parent;
+
+  private ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      CfClassSynthesizerDesugaringEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static CfClassSynthesizerDesugaringEventConsumer attach(
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
+      CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
+    if (artProfileCollectionAdditions.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingCfClassSynthesizerDesugaringEventConsumer(
+        artProfileCollectionAdditions.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptCollectionConversion(ProgramMethod arrayConversion) {
+    parent.acceptCollectionConversion(arrayConversion);
+  }
+
+  @Override
+  public void acceptWrapperProgramClass(DexProgramClass clazz) {
+    parent.acceptWrapperProgramClass(clazz);
+  }
+
+  @Override
+  public void acceptEnumConversionProgramClass(DexProgramClass clazz) {
+    parent.acceptEnumConversionProgramClass(clazz);
+  }
+
+  @Override
+  public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+    parent.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
+  }
+
+  @Override
+  public void acceptProgramEmulatedInterface(DexProgramClass clazz) {
+    parent.acceptProgramEmulatedInterface(clazz);
+  }
+
+  @Override
+  public void acceptRecordClass(DexProgramClass recordClass) {
+    parent.acceptRecordClass(recordClass);
+  }
+
+  @Override
+  public void acceptRecordClassContext(
+      DexProgramClass recordTagClass, DexProgramClass recordClass) {
+    additionsCollection.applyIfContextIsInProfile(
+        recordClass, additions -> additions.addClassRule(recordTagClass));
+    ProgramMethod recordTagInstanceInitializer = recordTagClass.getProgramDefaultInitializer();
+    if (recordTagInstanceInitializer != null) {
+      recordClass.forEachProgramInstanceInitializer(
+          recordInstanceInitializer ->
+              additionsCollection.applyIfContextIsInProfile(
+                  recordInstanceInitializer,
+                  additionsBuilder -> additionsBuilder.addRule(recordTagInstanceInitializer)));
+    }
+    parent.acceptRecordClassContext(recordTagClass, recordClass);
+  }
+
+  @Override
+  public void acceptVarHandleDesugaringClass(DexProgramClass varHandleClass) {
+    parent.acceptVarHandleDesugaringClass(varHandleClass);
+  }
+
+  @Override
+  public Set<DexProgramClass> getSynthesizedClasses() {
+    return parent.getSynthesizedClasses();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java
index 5b54cd3..811b1f5 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -11,6 +12,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.LambdaClass;
+import com.android.tools.r8.ir.desugar.LambdaClass.Target;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
 import java.util.List;
@@ -18,24 +20,28 @@
 public class ArtProfileRewritingCfInstructionDesugaringEventConsumer
     extends CfInstructionDesugaringEventConsumer {
 
+  private final AppView<?> appView;
   private final ConcreteArtProfileCollectionAdditions additionsCollection;
   private final CfInstructionDesugaringEventConsumer parent;
 
   private ArtProfileRewritingCfInstructionDesugaringEventConsumer(
+      AppView<?> appView,
       ConcreteArtProfileCollectionAdditions additionsCollection,
       CfInstructionDesugaringEventConsumer parent) {
+    this.appView = appView;
     this.additionsCollection = additionsCollection;
     this.parent = parent;
   }
 
   public static CfInstructionDesugaringEventConsumer attach(
+      AppView<?> appView,
       ArtProfileCollectionAdditions artProfileCollectionAdditions,
       CfInstructionDesugaringEventConsumer eventConsumer) {
     if (artProfileCollectionAdditions.isNop()) {
       return eventConsumer;
     }
     return new ArtProfileRewritingCfInstructionDesugaringEventConsumer(
-        artProfileCollectionAdditions.asConcrete(), eventConsumer);
+        appView, artProfileCollectionAdditions.asConcrete(), eventConsumer);
   }
 
   @Override
@@ -123,7 +129,7 @@
   @Override
   public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
     addLambdaClassAndInstanceInitializersIfSynthesizingContextIsInProfile(lambdaClass, context);
-    addLambdaVirtualMethodsIfLambdaImplementationIsInProfile(lambdaClass);
+    addLambdaVirtualMethodsIfLambdaImplementationIsInProfile(lambdaClass, context);
     parent.acceptLambdaClass(lambdaClass, context);
   }
 
@@ -141,18 +147,58 @@
         });
   }
 
-  private void addLambdaVirtualMethodsIfLambdaImplementationIsInProfile(LambdaClass lambdaClass) {
-    additionsCollection.applyIfContextIsInProfile(
-        lambdaClass.getTarget().getImplementationMethod(),
-        additionsBuilder -> {
-          lambdaClass
-              .getLambdaProgramClass()
-              .forEachProgramVirtualMethod(additionsBuilder::addRule);
-          if (lambdaClass.getTarget().getCallTarget()
-              != lambdaClass.getTarget().getImplementationMethod()) {
-            additionsBuilder.addRule(lambdaClass.getTarget().getCallTarget());
-          }
-        });
+  private void addLambdaVirtualMethodsIfLambdaImplementationIsInProfile(
+      LambdaClass lambdaClass, ProgramMethod context) {
+    if (shouldConservativelyAddLambdaVirtualMethodsIfLambdaInstantiated(lambdaClass, context)) {
+      additionsCollection.applyIfContextIsInProfile(
+          context,
+          additionsBuilder ->
+              lambdaClass
+                  .getLambdaProgramClass()
+                  .forEachProgramVirtualMethod(additionsBuilder::addRule));
+    } else {
+      Target target = lambdaClass.getTarget();
+      additionsCollection.applyIfContextIsInProfile(
+          target.getImplementationMethod(),
+          additionsBuilder -> {
+            lambdaClass
+                .getLambdaProgramClass()
+                .forEachProgramVirtualMethod(additionsBuilder::addRule);
+            if (target.getCallTarget() != target.getImplementationMethod()) {
+              additionsBuilder.addRule(target.getCallTarget());
+            }
+          });
+    }
+  }
+
+  private boolean shouldConservativelyAddLambdaVirtualMethodsIfLambdaInstantiated(
+      LambdaClass lambdaClass, ProgramMethod context) {
+    Target target = lambdaClass.getTarget();
+    if (target.getInvokeType().isInterface() || target.getInvokeType().isVirtual()) {
+      return true;
+    }
+    if (target.getImplementationMethod().getHolderType() == context.getHolderType()) {
+      // Direct call to the same class. Only add virtual methods if the callee is in the profile.
+      return false;
+    }
+    if (appView.hasClassHierarchy()) {
+      DexClassAndMethod resolutionResult =
+          appView
+              .appInfoWithClassHierarchy()
+              .resolveMethod(target.getImplementationMethod(), target.isInterface())
+              .getResolutionPair();
+      if (resolutionResult == null || resolutionResult.isProgramMethod()) {
+        // Direct call to other method in the app. Only add virtual methods if the callee is in the
+        // profile.
+        return false;
+      }
+      // The profile does not contain non-program items. Conservatively treat the call target as
+      // being executed.
+      return true;
+    } else {
+      // Should not lookup definitions outside the current context.
+      return true;
+    }
   }
 
   @Override
@@ -217,8 +263,37 @@
   }
 
   @Override
-  public void acceptRecordMethod(ProgramMethod method) {
-    parent.acceptRecordMethod(method);
+  public void acceptRecordClassContext(DexProgramClass recordTagClass, ProgramMethod context) {
+    parent.acceptRecordClassContext(recordTagClass, context);
+  }
+
+  @Override
+  public void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method));
+    parent.acceptRecordEqualsHelperMethod(method, context);
+  }
+
+  @Override
+  public void acceptRecordGetFieldsAsObjectsHelperMethod(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method));
+    parent.acceptRecordGetFieldsAsObjectsHelperMethod(method, context);
+  }
+
+  @Override
+  public void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptRecordHashCodeHelperMethod(method, context);
+  }
+
+  @Override
+  public void acceptRecordToStringHelperMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptRecordToStringHelperMethod(method, context);
   }
 
   @Override
@@ -234,11 +309,6 @@
   }
 
   @Override
-  public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
-    parent.acceptThrowMethod(method, context);
-  }
-
-  @Override
   public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
     additionsCollection.applyIfContextIsInProfile(
         context,
@@ -247,6 +317,41 @@
   }
 
   @Override
+  public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
+    parent.acceptUtilityToStringIfNotNullMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+      ProgramMethod method, ProgramMethod context) {
+    parent.acceptUtilityThrowClassCastExceptionIfNotNullMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowIllegalAccessErrorMethod(
+      ProgramMethod method, ProgramMethod context) {
+    parent.acceptUtilityThrowIllegalAccessErrorMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+      ProgramMethod method, ProgramMethod context) {
+    parent.acceptUtilityThrowIncompatibleClassChangeErrorMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowNoSuchMethodErrorMethod(
+      ProgramMethod method, ProgramMethod context) {
+    parent.acceptUtilityThrowNoSuchMethodErrorMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+      ProgramMethod method, ProgramMethod context) {
+    parent.acceptUtilityThrowRuntimeExceptionWithMessageMethod(method, context);
+  }
+
+  @Override
   public void acceptVarHandleDesugaringClass(DexProgramClass varHandleClass) {
     parent.acceptVarHandleDesugaringClass(varHandleClass);
   }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java
index 4309e8b..6843852 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingCfPostProcessingDesugaringEventConsumer.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
+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.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
 public class ArtProfileRewritingCfPostProcessingDesugaringEventConsumer
@@ -102,6 +104,11 @@
   }
 
   @Override
+  public Set<DexMethod> getNewlyLiveMethods() {
+    return parent.getNewlyLiveMethods();
+  }
+
+  @Override
   public void finalizeDesugaring() throws ExecutionException {
     parent.finalizeDesugaring();
   }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMethodProcessorEventConsumer.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMethodProcessorEventConsumer.java
new file mode 100644
index 0000000..325422e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileRewritingMethodProcessorEventConsumer.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2023, 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.profile.art.rewriting;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
+
+public class ArtProfileRewritingMethodProcessorEventConsumer extends MethodProcessorEventConsumer {
+
+  private final ConcreteArtProfileCollectionAdditions additionsCollection;
+  private final MethodProcessorEventConsumer parent;
+
+  private ArtProfileRewritingMethodProcessorEventConsumer(
+      ConcreteArtProfileCollectionAdditions additionsCollection,
+      MethodProcessorEventConsumer parent) {
+    this.additionsCollection = additionsCollection;
+    this.parent = parent;
+  }
+
+  public static MethodProcessorEventConsumer attach(
+      ArtProfileCollectionAdditions artProfileCollectionAdditions,
+      MethodProcessorEventConsumer eventConsumer) {
+    if (artProfileCollectionAdditions.isNop()) {
+      return eventConsumer;
+    }
+    return new ArtProfileRewritingMethodProcessorEventConsumer(
+        artProfileCollectionAdditions.asConcrete(), eventConsumer);
+  }
+
+  @Override
+  public void acceptEnumUnboxerCheckNotZeroContext(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method));
+    parent.acceptEnumUnboxerCheckNotZeroContext(method, context);
+  }
+
+  @Override
+  public void acceptEnumUnboxerLocalUtilityClassMethodContext(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptEnumUnboxerLocalUtilityClassMethodContext(method, context);
+  }
+
+  @Override
+  public void acceptEnumUnboxerSharedUtilityClassMethodContext(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context,
+        additionsBuilder -> {
+          additionsBuilder.addRule(method).addRule(method.getHolder());
+          method.getHolder().acceptProgramClassInitializer(additionsBuilder::addRule);
+        });
+    parent.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+  }
+
+  @Override
+  public void acceptInstanceInitializerOutline(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptInstanceInitializerOutline(method, context);
+  }
+
+  @Override
+  public void acceptUtilityToStringIfNotNullMethod(ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptUtilityToStringIfNotNullMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowClassCastExceptionIfNotNullMethod(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptUtilityThrowClassCastExceptionIfNotNullMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowIllegalAccessErrorMethod(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptUtilityThrowIllegalAccessErrorMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowIncompatibleClassChangeErrorMethod(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptUtilityThrowIncompatibleClassChangeErrorMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowNoSuchMethodErrorMethod(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptUtilityThrowNoSuchMethodErrorMethod(method, context);
+  }
+
+  @Override
+  public void acceptUtilityThrowRuntimeExceptionWithMessageMethod(
+      ProgramMethod method, ProgramMethod context) {
+    additionsCollection.applyIfContextIsInProfile(
+        context, additionsBuilder -> additionsBuilder.addRule(method).addRule(method.getHolder()));
+    parent.acceptUtilityThrowRuntimeExceptionWithMessageMethod(method, context);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
index 9c75c21..85c54e1 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
@@ -5,8 +5,10 @@
 package com.android.tools.r8.profile.art.rewriting;
 
 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.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.profile.art.ArtProfile;
 import com.android.tools.r8.profile.art.ArtProfileCollection;
 import com.android.tools.r8.profile.art.NonEmptyArtProfileCollection;
@@ -35,6 +37,17 @@
   }
 
   void applyIfContextIsInProfile(
+      DexClass context, Consumer<ArtProfileAdditions> additionsConsumer) {
+    applyIfContextIsInProfile(context.getType(), additionsConsumer);
+  }
+
+  void applyIfContextIsInProfile(DexType type, Consumer<ArtProfileAdditions> additionsConsumer) {
+    for (ArtProfileAdditions artProfileAdditions : additionsCollection) {
+      artProfileAdditions.applyIfContextIsInProfile(type, additionsConsumer);
+    }
+  }
+
+  public void applyIfContextIsInProfile(
       DexClassAndMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
     applyIfContextIsInProfile(context.getReference(), builderConsumer);
   }
@@ -48,7 +61,7 @@
   }
 
   @Override
-  ConcreteArtProfileCollectionAdditions asConcrete() {
+  public ConcreteArtProfileCollectionAdditions asConcrete() {
     return this;
   }
 
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 06db3b7..bbdb059 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3946,6 +3946,10 @@
       assert old == null || old == clazz;
     }
 
+    public Set<DexMethod> getNewlyLiveMethods() {
+      return liveMethods.keySet();
+    }
+
     public void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().getReference();
       ProgramMethod old = liveMethods.put(signature, method);
@@ -4579,9 +4583,8 @@
     InterfaceMethodProcessorFacade interfaceDesugaring =
         desugaring.getInterfaceMethodPostProcessingDesugaringR8(
             ExcludeDexResources, liveMethods::contains, interfaceProcessor);
-    CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring)
-        .postProcessingDesugaring(
-            liveTypes.items, liveMethods::contains, eventConsumer, executorService);
+    CfPostProcessingDesugaringCollection.create(appView, interfaceDesugaring, liveMethods::contains)
+        .postProcessingDesugaring(liveTypes.items, eventConsumer, executorService);
 
     if (syntheticAdditions.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 955069f..3eb32b9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
@@ -671,11 +672,11 @@
     StringBuilder builder = new StringBuilder();
     if (!keepAttributes.isEmpty()) {
       keepAttributes.append(builder);
-      builder.append('\n');
+      builder.append(StringUtils.LINE_SEPARATOR);
     }
     for (ProguardConfigurationRule rule : rules) {
       rule.append(builder);
-      builder.append('\n');
+      builder.append(StringUtils.LINE_SEPARATOR);
     }
     return builder.toString();
   }
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 bf4029e..d8ae369 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -91,7 +91,7 @@
           new ContextsForGlobalSyntheticsInSingleOutputMode() {
             @Override
             public void addGlobalContexts(
-                DexType globalType, Collection<ProgramDefinition> contexts) {
+                DexType globalType, Collection<? extends ProgramDefinition> contexts) {
               throw new Unreachable("Unexpected attempt to add globals to non-desugaring build.");
             }
           };
@@ -114,7 +114,7 @@
 
     void forEach(BiConsumer<DexType, Set<DexType>> fn);
 
-    void addGlobalContexts(DexType globalType, Collection<ProgramDefinition> contexts);
+    void addGlobalContexts(DexType globalType, Collection<? extends ProgramDefinition> contexts);
   }
 
   private static class ContextsForGlobalSyntheticsInSingleOutputMode
@@ -131,7 +131,8 @@
     }
 
     @Override
-    public void addGlobalContexts(DexType globalType, Collection<ProgramDefinition> contexts) {
+    public void addGlobalContexts(
+        DexType globalType, Collection<? extends ProgramDefinition> contexts) {
       // contexts are ignored in single output modes.
     }
   }
@@ -152,7 +153,8 @@
     }
 
     @Override
-    public void addGlobalContexts(DexType globalType, Collection<ProgramDefinition> contexts) {
+    public void addGlobalContexts(
+        DexType globalType, Collection<? extends ProgramDefinition> contexts) {
       Set<DexType> contextReferences =
           globalContexts.computeIfAbsent(globalType, k -> ConcurrentHashMap.newKeySet());
       contexts.forEach(definition -> contextReferences.add(definition.getContextType()));
@@ -976,7 +978,7 @@
       Supplier<MissingGlobalSyntheticsConsumerDiagnostic> diagnosticSupplier,
       SyntheticKindSelector kindSelector,
       DexType globalType,
-      Collection<ProgramDefinition> contexts,
+      Collection<? extends ProgramDefinition> contexts,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn,
       Consumer<DexProgramClass> onCreationConsumer) {
@@ -1033,7 +1035,8 @@
     pending.definitions.put(definition.getHolder().getType(), definition);
   }
 
-  private void addGlobalContexts(DexType globalType, Collection<ProgramDefinition> contexts) {
+  private void addGlobalContexts(
+      DexType globalType, Collection<? extends ProgramDefinition> contexts) {
     globalContexts.addGlobalContexts(globalType, contexts);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 69c9714..5be322e 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -43,14 +43,17 @@
   S(31),
   Sv2(32),
   T(33),
-  MASTER(34), // API level for master is tentative.
+  U(34),
+  MASTER(35), // API level for master is tentative.
   ANDROID_PLATFORM(10000);
 
   // When updating LATEST and a new version goes stable, add a new api-versions.xml to third_party
   // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest.
-  // TODO(b/204738868): Update API database for Sv2 / T when they are ready.
   public static final AndroidApiLevel LATEST = T;
 
+  // TODO(b/268601605): When adding U to the test matrix, set this to LATEST.
+  public static final AndroidApiLevel API_DATABASE_LEVEL = U;
+
   private final int level;
 
   AndroidApiLevel(int level) {
@@ -174,6 +177,8 @@
       case 33:
         return T;
       case 34:
+        return U;
+      case 35:
         return MASTER;
       case 10000:
         return ANDROID_PLATFORM;
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index 443b9e7..d094cf7 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -42,6 +42,7 @@
         // version.
       case ANDROID_PLATFORM:
       case MASTER:
+      case U:
       case T:
       case Sv2:
       case S:
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 2c06cad..e7dfeee 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1012,6 +1012,11 @@
   // If non-null, configuration must be passed to the consumer.
   public StringConsumer configurationConsumer = null;
 
+  public void resetDesugaredLibrarySpecificationForTesting() {
+    loadMachineDesugaredLibrarySpecification = null;
+    machineDesugaredLibrarySpecification = MachineDesugaredLibrarySpecification.empty();
+  }
+
   public void setDesugaredLibrarySpecification(DesugaredLibrarySpecification specification) {
     if (specification.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index 94b9bfe..e0a915f 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -61,6 +61,11 @@
     map.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
   }
 
+  public static <K, V> V removeOrDefault(Map<K, V> map, K key, V defaultValue) {
+    V value = map.remove(key);
+    return value != null ? value : defaultValue;
+  }
+
   public static String toString(Map<?, ?> map) {
     return StringUtils.join(
         ",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 251edcc..558ea43 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.references.TypeReference;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -74,6 +75,11 @@
     return Reference.method(type, "<init>", Collections.emptyList(), null);
   }
 
+  public static MethodReference instanceConstructor(
+      ClassReference type, TypeReference... formalTypes) {
+    return Reference.method(type, "<init>", Arrays.asList(formalTypes), null);
+  }
+
   public static int compare(MethodReference methodReference, ClassReference other) {
     return ClassReferenceUtils.compare(other, methodReference) * -1;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/SemanticVersion.java b/src/main/java/com/android/tools/r8/utils/SemanticVersion.java
index e8c0960..0bd7b67 100644
--- a/src/main/java/com/android/tools/r8/utils/SemanticVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/SemanticVersion.java
@@ -5,7 +5,11 @@
 
 import java.util.Objects;
 
-public class SemanticVersion {
+public class SemanticVersion implements Comparable<SemanticVersion> {
+
+  private static SemanticVersion MIN = SemanticVersion.create(0, 0, 0);
+  private static SemanticVersion MAX =
+      SemanticVersion.create(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
 
   public static SemanticVersion parse(String version) {
     int majorEnd = version.indexOf('.');
@@ -61,6 +65,14 @@
     return new SemanticVersion(major, minor, patch, prerelease);
   }
 
+  public static SemanticVersion min() {
+    return MIN;
+  }
+
+  public static SemanticVersion max() {
+    return MAX;
+  }
+
   public int getMajor() {
     return major;
   }
@@ -104,4 +116,12 @@
   public String toString() {
     return "" + major + "." + minor + "." + patch + (prerelease != null ? "-" + prerelease : "");
   }
+
+  @Override
+  public int compareTo(SemanticVersion other) {
+    if (equals(other)) {
+      return 0;
+    }
+    return isNewerOrEqual(other) ? -1 : 1;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index a5b9936..efe43d3 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -404,4 +404,12 @@
     }
     return stringToCapitalize.substring(0, 1).toUpperCase() + stringToCapitalize.substring(1);
   }
+
+  public static int indexOf(String s, char ch1, char ch2) {
+    int i1 = s.indexOf(ch1);
+    int i2 = s.indexOf(ch2);
+    if (i1 == -1) return i2;
+    if (i2 == -1) return i1;
+    return Math.min(i1, i2);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index 7f67d3d..b4ec7c9 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -210,9 +210,13 @@
 
       OneShotCollectionConsumer<MappingInformation> methodSpecificMappingInformation =
           OneShotCollectionConsumer.wrap(new ArrayList<>());
-      if (method.getDefinition().isD8R8Synthesized()
-          || (!mappedPositions.isEmpty()
-              && mappedPositions.get(0).getPosition().isD8R8Synthesized())) {
+      // We only do global synthetic classes when using names from the library. For such classes it
+      // is important that we do not filter out stack frames since they could appear from concrete
+      // classes in the library. Additionally, this is one place where it is helpful for developers
+      // to also get reported synthesized frames since stubbing can change control-flow and
+      // exceptions.
+      if (isD8R8Synthesized(method, mappedPositions)
+          && !appView.getSyntheticItems().isGlobalSyntheticClass(method.getHolder())) {
         methodSpecificMappingInformation.add(CompilerSynthesizedMappingInformation.getInstance());
       }
 
@@ -369,6 +373,12 @@
       return this;
     }
 
+    private boolean isD8R8Synthesized(ProgramMethod method, List<MappedPosition> mappedPositions) {
+      return method.getDefinition().isD8R8Synthesized()
+          || (!mappedPositions.isEmpty()
+              && mappedPositions.get(0).getPosition().isD8R8Synthesized());
+    }
+
     private MethodReference computeMappedMethod(DexMethod current, AppView<?> appView) {
       NamingLens namingLens = appView.getNamingLens();
       DexMethod renamedMethodSignature =
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index c4cb889..25628d7 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.SemanticVersion;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.nio.file.Path;
@@ -819,4 +820,9 @@
     builder.addStartupProfileProviders(startupProfileProviders);
     return self();
   }
+
+  public T setFakeCompilerVersion(SemanticVersion version) {
+    getBuilder().setFakeCompilerVersion(version);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 679584b..1ca26f0 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingBiConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -98,7 +99,12 @@
 
   @Override
   public CodeInspector inspector() throws IOException {
-    return new CodeInspector(app, proguardMap);
+    return inspector(null);
+  }
+
+  public CodeInspector inspector(Consumer<InternalOptions> debugOptionsConsumer)
+      throws IOException {
+    return new CodeInspector(app, proguardMap, debugOptionsConsumer);
   }
 
   private CodeInspector featureInspector(Path feature) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 1074507..aabdd99 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -94,6 +94,17 @@
     return false;
   }
 
+  public boolean canUseRecords() {
+    assert isCfRuntime() || isDexRuntime();
+    return isCfRuntime() && asCfRuntime().isNewerThanOrEqual(CfVm.JDK14);
+  }
+
+  public boolean canUseRecordsWhenDesugaring() {
+    assert isCfRuntime() || isDexRuntime();
+    assert apiLevel != null;
+    return false;
+  }
+
   // Convenience predicates.
   public boolean isDexRuntime() {
     return runtime.isDex();
@@ -182,6 +193,15 @@
     return this;
   }
 
+  public TestParameters assumeJvmTestParameters() {
+    assertFalse(
+        "No need to use assumeR8TestParameters() when not using api levels for CF",
+        apiLevel == null);
+    assumeCfRuntime();
+    assumeTrue(representativeApiLevelForRuntime);
+    return this;
+  }
+
   public TestParameters assumeR8TestParameters() {
     assertFalse(
         "No need to use assumeR8TestParameters() when not using api levels for CF",
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
index 423850e..b8d3f1f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -143,6 +143,7 @@
       Map<DexReference, AndroidApiLevel> referenceMap,
       Path androidJar) {
     Map<DexType, String> missingMemberInformation = new IdentityHashMap<>();
+    DexItemFactory factory = appView.dexItemFactory();
     for (DexLibraryClass clazz : appView.app().asDirect().libraryClasses()) {
       ParsedApiClass parsedApiClass = lookupMap.get(clazz.getClassReference());
       if (parsedApiClass == null) {
@@ -168,7 +169,7 @@
           method -> {
             if (method.getAccessFlags().isPublic()
                 && referenceMap.get(method.getReference()) == null
-                && !appView.dexItemFactory().objectMembers.isObjectMember(method.getReference())) {
+                && !factory.objectMembers.isObjectMember(method.getReference())) {
               classBuilder.append("  ").append(method).append(" is missing\n");
             }
           });
@@ -177,22 +178,30 @@
       }
     }
 
+    Set<DexType> expectedMissingMembers = new HashSet<>();
     // api-versions.xml do not encode all members of StringBuffers and StringBuilders, check that we
     // only have missing definitions for those two classes.
-    assert missingMemberInformation.size() == 7;
-    assert missingMemberInformation.containsKey(appView.dexItemFactory().stringBufferType);
-    assert missingMemberInformation.containsKey(appView.dexItemFactory().stringBuilderType);
+    expectedMissingMembers.add(factory.stringBufferType);
+    expectedMissingMembers.add(factory.stringBuilderType);
     // TODO(b/231126636): api-versions.xml has missing definitions for the below classes.
-    assert missingMemberInformation.containsKey(
-        appView.dexItemFactory().createType("Ljava/util/concurrent/ConcurrentHashMap$KeySetView;"));
-    assert missingMemberInformation.containsKey(
-        appView.dexItemFactory().createType("Ljava/time/chrono/ThaiBuddhistDate;"));
-    assert missingMemberInformation.containsKey(
-        appView.dexItemFactory().createType("Ljava/time/chrono/HijrahDate;"));
-    assert missingMemberInformation.containsKey(
-        appView.dexItemFactory().createType("Ljava/time/chrono/JapaneseDate;"));
-    assert missingMemberInformation.containsKey(
-        appView.dexItemFactory().createType("Ljava/time/chrono/MinguoDate;"));
+    expectedMissingMembers.add(
+        factory.createType("Ljava/util/concurrent/ConcurrentHashMap$KeySetView;"));
+    expectedMissingMembers.add(factory.createType("Ljava/time/chrono/ThaiBuddhistDate;"));
+    expectedMissingMembers.add(factory.createType("Ljava/time/chrono/HijrahDate;"));
+    expectedMissingMembers.add(factory.createType("Ljava/time/chrono/JapaneseDate;"));
+    expectedMissingMembers.add(factory.createType("Ljava/time/chrono/MinguoDate;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcV;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/IsoDep;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/MifareUltralight;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/MifareClassic;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NdefFormatable;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcA;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcBarcode;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcF;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcB;"));
+    expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/Ndef;"));
+    expectedMissingMembers.add(factory.createType("Landroid/webkit/CookieSyncManager;"));
+    assertEquals(expectedMissingMembers, missingMemberInformation.keySet());
     return true;
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 29c9709..285d96a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
@@ -46,7 +47,7 @@
           .resolve("new_api_database.ser");
 
   // Update the API_LEVEL below to have the database generated for a new api level.
-  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.LATEST;
+  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.API_DATABASE_LEVEL;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -94,18 +95,16 @@
     parsedApiClasses.forEach(
         apiClass -> {
           apiClass.visitFieldReferences(
-              ((apiLevel, fieldReferences) -> {
-                fieldReferences.forEach(field -> numberOfFields.increment());
-              }));
+              ((apiLevel, fieldReferences) ->
+                  fieldReferences.forEach(field -> numberOfFields.increment())));
           apiClass.visitMethodReferences(
-              ((apiLevel, methodReferences) -> {
-                methodReferences.forEach(field -> numberOfMethods.increment());
-              }));
+              ((AndroidApiLevel apiLevel, List<MethodReference> methodReferences) ->
+                  methodReferences.forEach(field -> numberOfMethods.increment())));
         });
     // These numbers will change when updating api-versions.xml
-    assertEquals(5272, parsedApiClasses.size());
-    assertEquals(27868, numberOfFields.get());
-    assertEquals(42268, numberOfMethods.get());
+    assertEquals(5635, parsedApiClasses.size());
+    assertEquals(29017, numberOfFields.get());
+    assertEquals(44107, numberOfMethods.get());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 97f6cd0..9f80832 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -19,9 +19,11 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.function.BiConsumer;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -48,11 +50,20 @@
     return parsedApiClass;
   }
 
+  private Set<String> getDeletedTypesMissingRemovedAttribute() {
+    Set<String> removedTypeNames = new HashSet<>();
+    if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.U)) {
+      removedTypeNames.add("com.android.internal.util.Predicate");
+    }
+    return removedTypeNames;
+  }
+
   private void readApiVersionsXmlFile() throws Exception {
     CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(maxApiLevel));
     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
     Document document = factory.newDocumentBuilder().parse(apiVersionsXml);
     NodeList classes = document.getElementsByTagName("class");
+    Set<String> exemptionList = getDeletedTypesMissingRemovedAttribute();
     for (int i = 0; i < classes.getLength(); i++) {
       Node node = classes.item(i);
       assert node.getNodeType() == Node.ELEMENT_NODE;
@@ -61,9 +72,10 @@
       ClassSubject clazz = inspector.clazz(type);
       if (!clazz.isPresent()) {
         if (!clazz.getOriginalName().startsWith("android.test")
-            && !clazz.getOriginalName().startsWith("junit")) {
-          assert hasRemoved(node);
-          assert getRemoved(node).isLessThanOrEqualTo(maxApiLevel);
+            && !clazz.getOriginalName().startsWith("junit")
+            && node.getAttributes().getNamedItem("module") == null) {
+          assert exemptionList.contains(type) || hasRemoved(node);
+          assert exemptionList.contains(type) || getRemoved(node).isLessThanOrEqualTo(maxApiLevel);
         }
         continue;
       }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockRetraceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockRetraceTest.java
new file mode 100644
index 0000000..edd4d27
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockRetraceTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2023, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.naming.retrace.StackTrace.containsLine;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace.EquivalenceWithoutFileNameAndLineNumber;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelMockRetraceTest extends TestBase {
+
+  private final AndroidApiLevel mockLevel = AndroidApiLevel.M;
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private boolean addToBootClasspath() {
+    return parameters.isCfRuntime()
+        || parameters.getRuntime().maxSupportedApiLevel().isGreaterThanOrEqualTo(mockLevel);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, ProgramClass.class)
+        .addLibraryClasses(LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion()
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(ProgramClass.class)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    StackTraceLine clinitFrame =
+        StackTraceLine.builder()
+            .setClassName(typeName(LibraryClass.class))
+            .setMethodName("<clinit>")
+            .build();
+    EquivalenceWithoutFileNameAndLineNumber equivalence =
+        EquivalenceWithoutFileNameAndLineNumber.get();
+    runResult.inspectOriginalStackTrace(
+        originalStackTrace ->
+            assertThat(originalStackTrace, containsLine(clinitFrame, equivalence)));
+    if (parameters.isCfRuntime()) {
+      runResult
+          .assertFailureWithErrorThatThrows(ExceptionInInitializerError.class)
+          .assertFailureWithErrorThatThrows(ArithmeticException.class)
+          .inspectStackTrace(
+              stackTrace -> assertThat(stackTrace, containsLine(clinitFrame, equivalence)));
+    } else {
+      runResult
+          .applyIf(
+              addToBootClasspath(),
+              result ->
+                  result
+                      .assertFailureWithErrorThatThrows(ExceptionInInitializerError.class)
+                      .assertFailureWithErrorThatThrows(ArithmeticException.class),
+              result -> result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class))
+          .inspectStackTrace(
+              stackTrace -> assertThat(stackTrace, containsLine(clinitFrame, equivalence)));
+    }
+  }
+
+  // Only present from 23.
+  public static class LibraryClass {
+
+    public static final int VALUE;
+
+    static {
+      VALUE = 2 / (System.currentTimeMillis() == 0 ? 1 : 0);
+    }
+  }
+
+  public static class ProgramClass extends LibraryClass {}
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      // Trigger a CL init that will either throw in the program stub or the library CL init.
+      Class.forName(LibraryClass.class.getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 1ca4309..1a744bb 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -82,6 +82,9 @@
       if (apiLevel == AndroidApiLevel.MASTER) {
         continue;
       }
+      if (apiLevel == AndroidApiLevel.U) {
+        continue;
+      }
       if (apiLevel == AndroidApiLevel.T) {
         continue;
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index a54633b..a6a0535 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -498,10 +498,10 @@
             libraryDesugaringSpecification.getSpecification(),
             libraryDesugaringSpecification.getDesugarJdkLibs(),
             out);
-    desugaredApi.run(targetApi.getLevel());
+    AndroidApiLevel compileApi = desugaredApi.run();
     return new CodeInspector(
-        out.resolve("compile_api_level_" + targetApi.getLevel())
-            .resolve("desugared_apis_" + targetApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
+        out.resolve("compile_api_level_" + compileApi.getLevel())
+            .resolve("desugared_apis_" + compileApi.getLevel() + "_" + minApi.getLevel() + ".jar"));
   }
 
   private boolean addType(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/GuavaMultiSetSpliteratorTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/GuavaMultiSetSpliteratorTest.java
new file mode 100644
index 0000000..c84d49f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/GuavaMultiSetSpliteratorTest.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2023, 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.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Multiset;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assume;
+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 GuavaMultiSetSpliteratorTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimes()
+            .withApiLevel(AndroidApiLevel.L)
+            .withAllApiLevels()
+            .build(),
+        getJdk8Jdk11(),
+        ImmutableList.of(D8_L8SHRINK, R8_L8SHRINK));
+  }
+
+  public GuavaMultiSetSpliteratorTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void testGuava() throws Throwable {
+    if (!compilationSpecification.isProgramShrink()) {
+      // We need multidex for non shrinking build.
+      Assume.assumeTrue(parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L));
+    }
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addProgramFiles(ToolHelper.DEPS)
+        .addInnerClasses(getClass())
+        .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
+        .allowDiagnosticWarningMessages()
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("17744", "NullPointerException");
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(ImmutableMultiset.of().spliterator().characteristics());
+      try {
+        System.out.println(new MyMultiSet<>().spliterator().characteristics());
+      } catch (Exception e) {
+        System.out.println(e.getClass().getSimpleName());
+      }
+    }
+  }
+
+  public static class MyMultiSet<E> implements Multiset<E> {
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public int count(@Nullable Object element) {
+      return 0;
+    }
+
+    @Override
+    public int add(@Nullable E element, int occurrences) {
+      return 0;
+    }
+
+    @Override
+    public boolean add(E element) {
+      return false;
+    }
+
+    @Override
+    public int remove(@Nullable Object element, int occurrences) {
+      return 0;
+    }
+
+    @Override
+    public boolean remove(@Nullable Object element) {
+      return false;
+    }
+
+    @Override
+    public int setCount(E element, int count) {
+      return 0;
+    }
+
+    @Override
+    public boolean setCount(E element, int oldCount, int newCount) {
+      return false;
+    }
+
+    @Override
+    public Set<E> elementSet() {
+      return null;
+    }
+
+    @Override
+    public Set<Entry<E>> entrySet() {
+      return null;
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] ts) {
+      return null;
+    }
+
+    @Override
+    public boolean contains(@Nullable Object element) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> elements) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> collection) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index a33e2d3..a29a6d5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
-import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8AndAll3Jdk11;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -18,11 +20,13 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateHtmlDoc;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateLintFiles;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -40,9 +44,11 @@
 
   private List<String> lintContents;
 
-  @Parameters(name = "{0}, spec: {1}")
+  @Parameters(name = "{1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8AndAll3Jdk11());
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(),
+        ImmutableList.of(JDK8, JDK11_MINIMAL, JDK11, JDK11_PATH, JDK11_LEGACY));
   }
 
   public LintFilesTest(
@@ -80,35 +86,21 @@
     assertTrue(supportsAllMethodsOf("java/util/Optional"));
     assertTrue(supportsAllMethodsOf("java/util/OptionalInt"));
 
-    // No parallel* methods pre L, and all stream methods supported from L.
+    // No parallel* methods pre L, Stream are never fully supported due to takeWhile/dropWhile.
     assertEquals(
         minApiLevel == AndroidApiLevel.L,
         supportsMethodButNotAllMethodsInClass(
             "java/util/Collection#parallelStream()Ljava/util/stream/Stream;"));
     assertEquals(
-        minApiLevel == AndroidApiLevel.L, supportsAllMethodsOf("java/util/stream/DoubleStream"));
-    assertFalse(
+        minApiLevel == AndroidApiLevel.L,
         supportsMethodButNotAllMethodsInClass(
             "java/util/stream/DoubleStream#parallel()Ljava/util/stream/DoubleStream;"));
-    assertFalse(
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/DoubleStream#parallel()Ljava/util/stream/BaseStream;"));
-    assertEquals(
-        minApiLevel == AndroidApiLevel.B,
+    assertTrue(
         supportsMethodButNotAllMethodsInClass(
             "java/util/stream/DoubleStream#allMatch(Ljava/util/function/DoublePredicate;)Z"));
+
     assertEquals(
-        minApiLevel == AndroidApiLevel.L, lintContents.contains("java/util/stream/IntStream"));
-    assertFalse(
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/IntStream#parallel()Ljava/util/stream/IntStream;"));
-    assertFalse(
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/IntStream#parallel()Ljava/util/stream/BaseStream;"));
-    assertEquals(
-        minApiLevel == AndroidApiLevel.B,
-        supportsMethodButNotAllMethodsInClass(
-            "java/util/stream/IntStream#allMatch(Ljava/util/function/IntPredicate;)Z"));
+        libraryDesugaringSpecification != JDK8, supportsAllMethodsOf("java/util/concurrent/Flow"));
 
     // Checks specific methods are supported or not in JDK8, all is supported in JDK11.
     if (libraryDesugaringSpecification == JDK8) {
@@ -133,7 +125,7 @@
 
     // Maintain type.
     assertEquals(
-        libraryDesugaringSpecification != JDK8,
+        libraryDesugaringSpecification != JDK8 && libraryDesugaringSpecification != JDK11_LEGACY,
         supportsAllMethodsOf("java/io/UncheckedIOException"));
 
     // Retarget method.
@@ -161,7 +153,7 @@
   }
 
   @Test
-  public void testFileContent() throws Exception {
+  public void testLint() throws Exception {
     Path directory = temp.newFolder().toPath();
     Path jdkLibJar =
         libraryDesugaringSpecification == JDK8
@@ -182,38 +174,36 @@
             false,
             AndroidApiLevel.B.getLevel());
 
-    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
-      if (apiLevel.isGreaterThan(AndroidApiLevel.T)) {
-        continue;
-      }
-      Path compileApiLevelDirectory = directory.resolve("compile_api_level_" + apiLevel.getLevel());
-      if (apiLevel.getLevel()
-          < desugaredLibrarySpecification.getRequiredCompilationApiLevel().getLevel()) {
-        System.out.println("!Checking " + compileApiLevelDirectory);
-        continue;
-      }
-      assertTrue(Files.exists(compileApiLevelDirectory));
-      for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
-        String desugaredApisBaseName =
-            "desugared_apis_" + apiLevel.getLevel() + "_" + minApiLevel.getLevel();
-        if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
-          assertTrue(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-          assertTrue(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
-          checkFileContent(
-              minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
-        } else {
-          assertFalse(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-          assertFalse(
-              Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
-        }
+    AndroidApiLevel requiredCompilationApiLevel =
+        desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+    Path compileApiLevelDirectory =
+        directory.resolve("compile_api_level_" + requiredCompilationApiLevel.getLevel());
+
+    assertTrue(Files.exists(compileApiLevelDirectory));
+    for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
+      String desugaredApisBaseName =
+          "desugared_apis_" + requiredCompilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
+      if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
+        assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+        assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
+        checkFileContent(
+            minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
+      } else {
+        assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
+        assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
       }
     }
+  }
+
+  @Test
+  public void testHTML() throws Exception {
+    Path jdkLibJar =
+        libraryDesugaringSpecification == JDK8
+            ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
+            : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
 
     Path directory2 = temp.newFolder().toPath();
-    GenerateLintFiles.main(
+    GenerateHtmlDoc.main(
         new String[] {
           "--generate-api-docs",
           libraryDesugaringSpecification.getSpecification().toString(),
@@ -225,5 +215,8 @@
     // check that the doc generation ran without error and looks sane.
     assertEquals("<tr>", html.get(0));
     assertEquals("</tr>", html.get(html.size() - 2));
+    if (libraryDesugaringSpecification == JDK11 || libraryDesugaringSpecification == JDK11_PATH) {
+      assertEquals(6, html.stream().filter(s -> s.contains("Flow")).count());
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
new file mode 100644
index 0000000..7240267
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2023, 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.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8AndAll3Jdk11;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedMethodsGenerator;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This test validates that anything supported at API N-1 is supported at API N. */
+@RunWith(Parameterized.class)
+public class PartialDesugaringTest extends DesugaredLibraryTestBase {
+
+  LibraryDesugaringSpecification librarySpecification;
+
+  @Parameters(name = "{0}, spec: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8AndAll3Jdk11());
+  }
+
+  public PartialDesugaringTest(
+      TestParameters parameters, LibraryDesugaringSpecification librarySpecification) {
+    assert parameters.isNoneRuntime();
+    this.librarySpecification = librarySpecification;
+  }
+
+  private static List<AndroidApiLevel> getRelevantApiLevels() {
+    // B is implicit - everything is supported on B.
+    return ImmutableList.of(
+        AndroidApiLevel.K,
+        AndroidApiLevel.L,
+        AndroidApiLevel.M,
+        AndroidApiLevel.N,
+        AndroidApiLevel.O,
+        AndroidApiLevel.P,
+        AndroidApiLevel.Q,
+        AndroidApiLevel.R,
+        AndroidApiLevel.S,
+        AndroidApiLevel.T);
+  }
+
+  // TODO(b/268425188): Fix remaining failures.
+  private static final Set<String> FAILURES_STREAM =
+      ImmutableSet.of(
+          // The takeWhile/dropWhile methods are not yet present on android.jar.
+          "java.util.stream.IntStream"
+              + " java.util.stream.IntStream.dropWhile(java.util.function.IntPredicate)",
+          "java.util.stream.Stream java.util.stream.Stream.takeWhile(java.util.function.Predicate)",
+          "java.util.stream.LongStream"
+              + " java.util.stream.LongStream.dropWhile(java.util.function.LongPredicate)",
+          "java.util.stream.DoubleStream"
+              + " java.util.stream.DoubleStream.takeWhile(java.util.function.DoublePredicate)",
+          "java.util.stream.IntStream"
+              + " java.util.stream.IntStream.takeWhile(java.util.function.IntPredicate)",
+          "java.util.stream.Stream java.util.stream.Stream.dropWhile(java.util.function.Predicate)",
+          "java.util.stream.LongStream"
+              + " java.util.stream.LongStream.takeWhile(java.util.function.LongPredicate)",
+          "java.util.stream.DoubleStream"
+              + " java.util.stream.DoubleStream.dropWhile(java.util.function.DoublePredicate)");
+  private static final Set<String> FAILURES_FILE_STORE =
+      ImmutableSet.of(
+          // FileStore.getBlockSize() was added in 33.
+          "long java.nio.file.FileStore.getBlockSize()");
+  private static final Set<String> FAILURES_SUMMARY_STATISTICS =
+      ImmutableSet.of(
+          "void java.util.LongSummaryStatistics.<init>(long, long, long, long)",
+          "void java.util.IntSummaryStatistics.<init>(long, int, int, long)");
+  // For some reason, in Android T, the other constructor were added but not this one...
+  private static final Set<String> FAILURES_DOUBLE_SUMMARY_STATISTICS =
+      ImmutableSet.of(
+          "void java.util.DoubleSummaryStatistics.<init>(long, double, double, double)");
+  private static final Set<String> FAILURES_TO_ARRAY =
+      ImmutableSet.of(
+          // See b/266401747: Desugaring is disabled.
+          "java.lang.Object[] java.util.Collection.toArray(java.util.function.IntFunction)");
+  private static final Set<String> FAILURES_ERA =
+      ImmutableSet.of(
+          // This fails on Java 8 desugared library due to missing covariant return type.
+          // The method is present on platform from 33 but not in android.jar...
+          "java.time.chrono.IsoEra java.time.LocalDate.getEra()");
+  private static final Set<String> FAILURES_CHRONOLOGY =
+      ImmutableSet.of(
+          "long java.time.chrono.Chronology.epochSecond(int, int, int, int, int, int,"
+              + " java.time.ZoneOffset)",
+          "long java.time.chrono.Chronology.epochSecond(java.time.chrono.Era, int, int, int, int,"
+              + " int, int, java.time.ZoneOffset)",
+          "long java.time.chrono.IsoChronology.epochSecond(int, int, int, int, int, int,"
+              + " java.time.ZoneOffset)");
+  private static final Set<String> FAILURES_DATE_TIME_BUILDER =
+      ImmutableSet.of(
+          "java.time.format.DateTimeFormatterBuilder"
+              + " java.time.format.DateTimeFormatterBuilder.appendGenericZoneText(java.time.format.TextStyle)",
+          "java.time.format.DateTimeFormatterBuilder"
+              + " java.time.format.DateTimeFormatterBuilder.appendGenericZoneText(java.time.format.TextStyle,"
+              + " java.util.Set)");
+
+  @Test
+  public void test() throws Exception {
+    SupportedClasses supportedClasses =
+        new SupportedMethodsGenerator(new InternalOptions())
+            .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification());
+
+    for (AndroidApiLevel api : getRelevantApiLevels()) {
+      Set<DexMethod> localFailures = Sets.newIdentityHashSet();
+      supportedClasses.forEachClass(
+          supportedClass ->
+              supportedClass.forEachMethodAndAnnotation(
+                  (method, annotation) -> {
+                    if (annotation != null && annotation.isUnsupportedInMinApiRange()) {
+                      if (api.getLevel() >= annotation.getMinRange()
+                          && api.getLevel() <= annotation.getMaxRange()) {
+                        localFailures.add(method.getReference());
+                      }
+                    }
+                  }));
+      Set<String> expectedFailures = getExpectedFailures(api);
+      Set<String> apiFailuresString =
+          localFailures.stream().map(DexMethod::toString).collect(Collectors.toSet());
+      if (!expectedFailures.equals(apiFailuresString)) {
+        System.out.println("Failure for api " + api);
+        assertEquals(expectedFailures, apiFailuresString);
+      }
+    }
+  }
+
+  private Set<String> getExpectedFailures(AndroidApiLevel api) {
+    Set<String> expectedFailures = new HashSet<>();
+    boolean jdk11NonMinimal = librarySpecification != JDK8 && librarySpecification != JDK11_MINIMAL;
+    if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
+      expectedFailures.addAll(FAILURES_STREAM);
+      expectedFailures.addAll(FAILURES_DOUBLE_SUMMARY_STATISTICS);
+      if (api.isLessThan(AndroidApiLevel.T)) {
+        expectedFailures.addAll(FAILURES_SUMMARY_STATISTICS);
+      }
+    }
+    if (librarySpecification == JDK11_PATH
+        && api.isGreaterThanOrEqualTo(AndroidApiLevel.O)
+        && api.isLessThan(AndroidApiLevel.T)) {
+      expectedFailures.addAll(FAILURES_FILE_STORE);
+    }
+    if (librarySpecification != JDK11_MINIMAL
+        && api.isGreaterThanOrEqualTo(AndroidApiLevel.N)
+        && api.isLessThan(AndroidApiLevel.T)) {
+      expectedFailures.addAll(FAILURES_TO_ARRAY);
+    }
+    if (librarySpecification == JDK8 && api.isLessThan(AndroidApiLevel.T)) {
+      // Interestingly that was added somehow to JDK8 desugared library at some point...
+      expectedFailures.addAll(FAILURES_TO_ARRAY);
+    }
+    if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+      expectedFailures.addAll(FAILURES_CHRONOLOGY);
+      expectedFailures.addAll(FAILURES_DATE_TIME_BUILDER);
+    }
+    if (librarySpecification == JDK8 && api.isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+      expectedFailures.addAll(FAILURES_ERA);
+    }
+    if (jdk11NonMinimal && api.isGreaterThanOrEqualTo(AndroidApiLevel.T)) {
+      // The method is present, but not in android.jar...
+      expectedFailures.addAll(FAILURES_ERA);
+    }
+    return expectedFailures;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
index de13512..eace966 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
@@ -66,12 +66,15 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(
             jars.getForConfiguration(kotlinParameters),
-            kotlinParameters.getCompiler().getKotlinStdlibJar())
+            kotlinParameters.getCompiler().getKotlinStdlibJar(),
+            kotlinParameters.getCompiler().getKotlinAnnotationJar())
         .addKeepMainRule(PKG + ".MainKt")
         .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepRuntimeVisibleAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(PKG + ".Color"))
+        // TODO(b/268005228): We should be able to unbox.
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertUnboxedIf(!kotlinParameters.isKotlinDev(), PKG + ".Color"))
         .allowDiagnosticMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
index eef3dcf..c2d3a03 100644
--- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
+++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -48,6 +48,8 @@
   public void testR8() throws Exception {
     testForR8(Backend.DEX)
         .addProgramFiles(outDirectory.resolve("program.jar"))
+        .addOptionsModification(
+            options -> options.getArtProfileOptions().setEnableCompletenessCheckForTesting(true))
         .apply(this::configure)
         .compile();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 2e3512d..8c83a79 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
 import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.origin.Origin;
@@ -220,6 +221,11 @@
     }
 
     @Override
+    public MethodProcessorEventConsumer getEventConsumer() {
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean shouldApplyCodeRewritings(ProgramMethod method) {
       return false;
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java
index 92383ae..3787be5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineKeepMethodTest.java
@@ -9,19 +9,15 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoVerticalClassMerging;
 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 com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,10 +28,41 @@
   private final TestParameters parameters;
   private static final String EXPECTED_OUTPUT = "Hello world";
 
-  @NoVerticalClassMerging
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntime(Version.last()).build();
+  }
+
+  public ClassInlineKeepMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testIsKeptWithName()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlineKeepMethodTest.class)
+        .addKeepMainRule(Keeper.class)
+        .addKeepClassAndMembersRules(ShouldBeKept.class)
+        .run(parameters.getRuntime(), Keeper.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        .inspect(
+            inspector -> {
+              ClassSubject shouldBeKeptClass = inspector.clazz(ShouldBeKept.class);
+              assertThat(shouldBeKeptClass, isPresent());
+              assertThat(
+                  shouldBeKeptClass.uniqueMethodWithOriginalName("shouldBeKept"), isPresent());
+              // Verify that we did not inline from the method by checking for a const string.
+              ClassSubject clazz = inspector.clazz(Keeper.class);
+              assertThat(clazz, isPresent());
+              MethodSubject main = clazz.uniqueMethodWithOriginalName("main");
+              assertTrue(
+                  main.streamInstructions().noneMatch(i -> i.isConstString(JumboStringMode.ALLOW)));
+            });
+  }
+
   public static class ShouldBeKept {
 
-    @NeverInline
     public void shouldBeKept() {
       System.out.print("Hello world");
     }
@@ -48,32 +75,4 @@
     }
   }
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntime(Version.last()).build();
-  }
-
-  public ClassInlineKeepMethodTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Ignore("b/129390240")
-  @Test
-  public void testIsKeptWithName()
-      throws ExecutionException, CompilationFailedException, IOException {
-    CodeInspector inspector =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(ClassInlineKeepMethodTest.class)
-            .addKeepMainRule(Keeper.class)
-            .addKeepClassAndMembersRules(ShouldBeKept.class)
-            .enableInliningAnnotations()
-            .enableNoVerticalClassMergingAnnotations()
-            .run(parameters.getRuntime(), Keeper.class)
-            .assertSuccessWithOutput(EXPECTED_OUTPUT)
-            .inspector();
-    ClassSubject clazz = inspector.clazz(Keeper.class);
-    assertThat(clazz, isPresent());
-    MethodSubject main = clazz.uniqueMethodWithOriginalName("main");
-    assertTrue(main.streamInstructions().noneMatch(i -> i.isConstString(JumboStringMode.ALLOW)));
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index 7b07978..36efb33 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -122,19 +122,24 @@
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
             .compile()
             .writeToZip();
+    Path outputPath = temp.newFolder().toPath();
     ProcessResult compileResult =
         kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
             .addClasspathFiles(outputJar)
             .addSourceFiles(
                 getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
-            .setOutputPath(temp.newFolder().toPath())
+            .setOutputPath(outputPath)
             .compileRaw();
-    Assert.assertEquals(1, compileResult.exitCode);
     if (kotlinParameters.isNewerThan(KOTLINC_1_8_0)) {
-      assertThat(
-          compileResult.stderr,
-          containsString("the feature \"references to synthetic java properties\""));
+      testForJvm()
+          .addRunClasspathFiles(
+              kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), outputJar)
+          .addClasspath(outputPath)
+          .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+          .assertSuccessWithOutputLines(
+              "foobar", "property oldName (Kotlin reflection is not available)");
     } else {
+      Assert.assertEquals(1, compileResult.exitCode);
       assertThat(
           compileResult.stderr,
           containsString(
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
index 0963963..d591306 100644
--- a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.KotlinCompilerTool;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -61,6 +62,13 @@
             })
         .setMinApi(parameters.getApiLevel())
         .addDontObfuscate()
+        // This will probably start failing when the CL
+        // https://github.com/JetBrains/kotlin/commit/79f6d4b590573e6adccd7e8899d3b15ddb42d185
+        // is propagated to the build for kotlin-reflect.
+        .applyIf(
+            parameters.isDexRuntime()
+                && kotlinParameters.isNewerThan(KotlinCompilerVersion.KOTLINC_1_8_0),
+            b -> b.addDontWarn("java.lang.ClassValue"))
         .allowDiagnosticWarningMessages()
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 87698d9..0446cd5 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -776,4 +776,31 @@
       StackTrace stackTrace, StackTraceLine lineToIgnoreLineNumberFor) {
     return new StackTraceIgnoreSpecificLineNumberMatcher(stackTrace, lineToIgnoreLineNumberFor);
   }
+
+  public static Matcher<StackTrace> containsLine(
+      StackTraceLine expected, StackTraceEquivalence equivalence) {
+    return new TypeSafeMatcher<StackTrace>() {
+
+      private final Equivalence<StackTrace.StackTraceLine> lineEquivalence =
+          equivalence.getLineEquivalence();
+
+      @Override
+      public boolean matchesSafely(StackTrace stackTrace) {
+        for (StackTraceLine actual : stackTrace.getStackTraceLines()) {
+          if (lineEquivalence.equivalent(actual, expected)) {
+            return true;
+          }
+        }
+        return false;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description
+            .appendText("stacktrace did not match")
+            .appendText(System.lineSeparator())
+            .appendText(expected.toString());
+      }
+    };
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
index 13a8c9d..e9058d4 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
@@ -88,18 +88,25 @@
             SyntheticItemsTestUtils.syntheticEnumUnboxingSharedUtilityClass(MyEnum.class));
     assertThat(enumUnboxingSharedUtilityClassSubject, isPresent());
     assertThat(enumUnboxingSharedUtilityClassSubject.clinit(), isPresent());
-    assertThat(
-        enumUnboxingSharedUtilityClassSubject.uniqueMethodWithOriginalName("ordinal"), isPresent());
-    assertThat(
-        enumUnboxingSharedUtilityClassSubject.uniqueMethodWithOriginalName("values"), isPresent());
 
-    // TODO(b/265729283): Should also include the above methods from enum unboxing.
+    MethodSubject sharedOrdinalMethodSubject =
+        enumUnboxingSharedUtilityClassSubject.uniqueMethodWithOriginalName("ordinal");
+    assertThat(sharedOrdinalMethodSubject, isPresent());
+
+    MethodSubject sharedValuesMethodSubject =
+        enumUnboxingSharedUtilityClassSubject.uniqueMethodWithOriginalName("values");
+    assertThat(sharedValuesMethodSubject, isPresent());
+
     profileInspector
+        .assertContainsClassRule(enumUnboxingSharedUtilityClassSubject)
         .assertContainsMethodRules(
             mainClassSubject.mainMethod(),
             localGreetMethodSubject,
             localOtherMethodSubject,
-            localValuesMethodSubject)
+            localValuesMethodSubject,
+            enumUnboxingSharedUtilityClassSubject.clinit(),
+            sharedOrdinalMethodSubject,
+            sharedValuesMethodSubject)
         .assertContainsNoOtherRules();
   }
 
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java
new file mode 100644
index 0000000..ee456d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/LambdaStaticLibraryMethodImplementationProfileRewritingTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LambdaStaticLibraryMethodImplementationProfileRewritingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addArtProfileForRewriting(getArtProfile())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectResidualArtProfile(this::inspectD8)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addArtProfileForRewriting(getArtProfile())
+        .addOptionsModification(InlinerOptions::disableInlining)
+        .addOptionsModification(
+            options -> options.callSiteOptimizationOptions().setEnableMethodStaticizing(false))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectResidualArtProfile(this::inspectR8)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  public ExternalArtProfile getArtProfile() {
+    return ExternalArtProfile.builder()
+        .addMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+        .build();
+  }
+
+  private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) {
+    inspect(profileInspector, inspector, false);
+  }
+
+  private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) {
+    inspect(profileInspector, inspector, parameters.isCfRuntime());
+  }
+
+  public void inspect(
+      ArtProfileInspector profileInspector, CodeInspector inspector, boolean canUseLambdas) {
+    ClassSubject mainClassSubject = inspector.clazz(Main.class);
+    assertThat(mainClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+
+    // Check the presence of the lambda class and its methods.
+    ClassSubject lambdaClassSubject =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0));
+    assertThat(lambdaClassSubject, notIf(isPresent(), canUseLambdas));
+
+    MethodSubject lambdaInitializerSubject = lambdaClassSubject.uniqueInstanceInitializer();
+    assertThat(lambdaInitializerSubject, notIf(isPresent(), canUseLambdas));
+
+    MethodSubject lambdaMainMethodSubject =
+        lambdaClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+    assertThat(lambdaMainMethodSubject, notIf(isPresent(), canUseLambdas));
+
+    if (canUseLambdas) {
+      profileInspector.assertContainsMethodRule(mainMethodSubject).assertContainsNoOtherRules();
+    } else {
+      profileInspector
+          .assertContainsClassRules(lambdaClassSubject)
+          .assertContainsMethodRules(
+              mainMethodSubject, lambdaInitializerSubject, lambdaMainMethodSubject)
+          .assertContainsNoOtherRules();
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      SetSupplier lambda = Collections::emptySet;
+      System.out.println(lambda.get().size());
+    }
+  }
+
+  interface SetSupplier {
+
+    Set<?> get();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
new file mode 100644
index 0000000..0969070
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -0,0 +1,300 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.ir.desugar.records.RecordDesugaring.EQUALS_RECORD_METHOD_NAME;
+import static com.android.tools.r8.ir.desugar.records.RecordDesugaring.GET_FIELDS_AS_OBJECTS_METHOD_NAME;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.ifThen;
+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.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.records.RecordTestUtils;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+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.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RecordProfileRewritingTest extends TestBase {
+
+  private static final String RECORD_NAME = "SimpleRecord";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
+
+  private static final ClassReference MAIN_REFERENCE =
+      Reference.classFromTypeName(RecordTestUtils.getMainType(RECORD_NAME));
+  private static final ClassReference PERSON_REFERENCE =
+      Reference.classFromTypeName(MAIN_REFERENCE.getTypeName() + "$Person");
+  private static final ClassReference RECORD_REFERENCE =
+      Reference.classFromTypeName("java.lang.Record");
+  private static final ClassReference OBJECT_REFERENCE = Reference.classFromClass(Object.class);
+  private static final ClassReference STRING_REFERENCE = Reference.classFromClass(String.class);
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    parameters.assumeJvmTestParameters();
+    assumeTrue(parameters.canUseRecords());
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    D8TestCompileResult compileResult =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .addArtProfileForRewriting(getArtProfile())
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    compileResult
+        .inspectResidualArtProfile(
+            profileInspector ->
+                compileResult.inspectWithOptions(
+                    inspector -> inspectD8(profileInspector, inspector),
+                    options -> options.testing.disableRecordApplicationReaderMap = true))
+        .run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    assumeTrue(parameters.canUseRecords() || parameters.isDexRuntime());
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .addKeepMainRule(MAIN_REFERENCE.getTypeName())
+            .addKeepRules(
+                "-neverpropagatevalue class " + PERSON_REFERENCE.getTypeName() + " { <fields>; }")
+            .addArtProfileForRewriting(getArtProfile())
+            .addOptionsModification(InlinerOptions::disableInlining)
+            .applyIf(
+                parameters.isCfRuntime(),
+                testBuilder ->
+                    testBuilder.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)))
+            .enableProguardTestOptions()
+            .noHorizontalClassMergingOfSynthetics()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    compileResult
+        .inspectResidualArtProfile(
+            profileInspector ->
+                compileResult.inspectWithOptions(
+                    inspector -> inspectR8(profileInspector, inspector),
+                    options -> options.testing.disableRecordApplicationReaderMap = true))
+        .run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private ExternalArtProfile getArtProfile() {
+    return ExternalArtProfile.builder()
+        .addMethodRule(MethodReferenceUtils.mainMethod(MAIN_REFERENCE))
+        .addClassRule(PERSON_REFERENCE)
+        .addMethodRule(
+            MethodReferenceUtils.instanceConstructor(
+                PERSON_REFERENCE, STRING_REFERENCE, Reference.INT))
+        .addMethodRule(
+            Reference.method(PERSON_REFERENCE, "name", Collections.emptyList(), STRING_REFERENCE))
+        .addMethodRule(
+            Reference.method(PERSON_REFERENCE, "age", Collections.emptyList(), Reference.INT))
+        .addMethodRule(
+            Reference.method(
+                PERSON_REFERENCE, "equals", ImmutableList.of(OBJECT_REFERENCE), Reference.BOOL))
+        .addMethodRule(
+            Reference.method(PERSON_REFERENCE, "hashCode", Collections.emptyList(), Reference.INT))
+        .addMethodRule(
+            Reference.method(
+                PERSON_REFERENCE, "toString", Collections.emptyList(), STRING_REFERENCE))
+        .build();
+  }
+
+  private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) {
+    inspect(
+        profileInspector,
+        inspector,
+        SyntheticItemsTestUtils.syntheticRecordTagClass(),
+        parameters.canUseNestBasedAccessesWhenDesugaring(),
+        parameters.canUseRecordsWhenDesugaring());
+  }
+
+  private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) {
+    inspect(
+        profileInspector,
+        inspector,
+        RECORD_REFERENCE,
+        parameters.canUseNestBasedAccesses(),
+        parameters.canUseRecords());
+  }
+
+  private void inspect(
+      ArtProfileInspector profileInspector,
+      CodeInspector inspector,
+      ClassReference recordClassReference,
+      boolean canUseNestBasedAccesses,
+      boolean canUseRecords) {
+    ClassSubject mainClassSubject = inspector.clazz(MAIN_REFERENCE);
+    assertThat(mainClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+
+    ClassSubject recordTagClassSubject = inspector.clazz(recordClassReference);
+    assertThat(recordTagClassSubject, notIf(isPresent(), canUseRecords));
+    if (!canUseRecords) {
+      assertEquals(1, recordTagClassSubject.allMethods().size());
+    }
+
+    MethodSubject recordTagInstanceInitializerSubject = recordTagClassSubject.init();
+    assertThat(recordTagInstanceInitializerSubject, notIf(isPresent(), canUseRecords));
+
+    ClassSubject personRecordClassSubject = inspector.clazz(PERSON_REFERENCE);
+    assertThat(personRecordClassSubject, isPresent());
+    assertEquals(
+        canUseRecords
+            ? inspector.getTypeSubject(RECORD_REFERENCE.getTypeName())
+            : recordTagClassSubject.asTypeSubject(),
+        personRecordClassSubject.getSuperType());
+    assertEquals(canUseRecords ? 6 : 10, personRecordClassSubject.allMethods().size());
+
+    MethodSubject personInstanceInitializerSubject =
+        personRecordClassSubject.uniqueInstanceInitializer();
+    assertThat(personInstanceInitializerSubject, isPresent());
+
+    // Name getters.
+    MethodSubject nameMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("name");
+    assertThat(nameMethodSubject, isPresent());
+
+    MethodSubject nameNestAccessorMethodSubject =
+        personRecordClassSubject.uniqueMethodWithOriginalName(
+            SyntheticItemsTestUtils.syntheticNestInstanceFieldGetter(
+                    Reference.field(PERSON_REFERENCE, "name", STRING_REFERENCE))
+                .getMethodName());
+    assertThat(nameNestAccessorMethodSubject, notIf(isPresent(), canUseNestBasedAccesses));
+
+    // Age getters.
+    MethodSubject ageMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("age");
+    assertThat(ageMethodSubject, isPresent());
+
+    MethodSubject ageNestAccessorMethodSubject =
+        personRecordClassSubject.uniqueMethodWithOriginalName(
+            SyntheticItemsTestUtils.syntheticNestInstanceFieldGetter(
+                    Reference.field(PERSON_REFERENCE, "age", Reference.INT))
+                .getMethodName());
+    assertThat(ageNestAccessorMethodSubject, notIf(isPresent(), canUseNestBasedAccesses));
+
+    // boolean equals(Object)
+    MethodSubject getFieldsAsObjectsMethodSubject =
+        personRecordClassSubject.uniqueMethodWithOriginalName(GET_FIELDS_AS_OBJECTS_METHOD_NAME);
+    assertThat(getFieldsAsObjectsMethodSubject, notIf(isPresent(), canUseRecords));
+
+    MethodSubject equalsHelperMethodSubject =
+        personRecordClassSubject.uniqueMethodWithOriginalName(EQUALS_RECORD_METHOD_NAME);
+    assertThat(equalsHelperMethodSubject, notIf(isPresent(), canUseRecords));
+
+    MethodSubject equalsMethodSubject =
+        personRecordClassSubject.uniqueMethodWithOriginalName("equals");
+    assertThat(equalsMethodSubject, isPresent());
+    assertThat(
+        equalsMethodSubject, ifThen(!canUseRecords, invokesMethod(equalsHelperMethodSubject)));
+
+    // int hashCode()
+    ClassSubject hashCodeHelperClassSubject =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 0));
+    assertThat(hashCodeHelperClassSubject, notIf(isPresent(), canUseRecords));
+
+    MethodSubject hashCodeHelperMethodSubject = hashCodeHelperClassSubject.uniqueMethod();
+    assertThat(hashCodeHelperMethodSubject, notIf(isPresent(), canUseRecords));
+
+    MethodSubject hashCodeMethodSubject =
+        personRecordClassSubject.uniqueMethodWithOriginalName("hashCode");
+    assertThat(hashCodeMethodSubject, isPresent());
+    assertThat(
+        hashCodeMethodSubject,
+        ifThen(!canUseRecords, invokesMethod(getFieldsAsObjectsMethodSubject)));
+    assertThat(
+        hashCodeMethodSubject, ifThen(!canUseRecords, invokesMethod(hashCodeHelperMethodSubject)));
+
+    // String toString()
+    ClassSubject toStringHelperClassSubject =
+        inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 1));
+    assertThat(toStringHelperClassSubject, notIf(isPresent(), canUseRecords));
+
+    MethodSubject toStringHelperMethodSubject = toStringHelperClassSubject.uniqueMethod();
+    assertThat(toStringHelperMethodSubject, notIf(isPresent(), canUseRecords));
+
+    MethodSubject toStringMethodSubject =
+        personRecordClassSubject.uniqueMethodWithOriginalName("toString");
+    assertThat(toStringMethodSubject, isPresent());
+    assertThat(
+        toStringMethodSubject,
+        ifThen(!canUseRecords, invokesMethod(getFieldsAsObjectsMethodSubject)));
+    assertThat(
+        toStringMethodSubject, ifThen(!canUseRecords, invokesMethod(toStringHelperMethodSubject)));
+
+    profileInspector
+        .assertContainsClassRule(personRecordClassSubject)
+        .assertContainsMethodRules(
+            mainMethodSubject,
+            personInstanceInitializerSubject,
+            nameMethodSubject,
+            ageMethodSubject,
+            equalsMethodSubject,
+            hashCodeMethodSubject,
+            toStringMethodSubject)
+        .applyIf(
+            !canUseNestBasedAccesses,
+            i ->
+                i.assertContainsMethodRules(
+                    nameNestAccessorMethodSubject, ageNestAccessorMethodSubject))
+        .applyIf(
+            !canUseRecords,
+            i ->
+                i.assertContainsClassRules(
+                        recordTagClassSubject,
+                        hashCodeHelperClassSubject,
+                        toStringHelperClassSubject)
+                    .assertContainsMethodRules(
+                        recordTagInstanceInitializerSubject,
+                        equalsHelperMethodSubject,
+                        getFieldsAsObjectsMethodSubject,
+                        hashCodeHelperMethodSubject,
+                        toStringHelperMethodSubject))
+        .assertContainsNoOtherRules();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceSyntheticClassWithNonSyntheticMethodTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceSyntheticClassWithNonSyntheticMethodTest.java
new file mode 100644
index 0000000..9b8a324
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceSyntheticClassWithNonSyntheticMethodTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, 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.retrace;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+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 RetraceSyntheticClassWithNonSyntheticMethodTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RetraceSyntheticClassWithNonSyntheticMethodTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  private final ClassReference originalClass = Reference.classFromTypeName("some.Class");
+  private final ClassReference obfuscatedClass = Reference.classFromTypeName("a");
+
+  private final String mapping =
+      StringUtils.lines(
+          "# { id: 'com.android.tools.r8.mapping', version: '1.0' }",
+          originalClass.getTypeName() + " -> " + obfuscatedClass.getTypeName() + ":",
+          "  # { id: 'com.android.tools.r8.synthesized' }",
+          "  void <clinit>() -> <clinit>");
+
+  @Test
+  public void retraceSyntheticTest() {
+    Retracer retracer =
+        Retracer.createDefault(
+            ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+    RetraceClassResult retraceClassResult = retracer.retraceClass(obfuscatedClass);
+    List<RetraceClassElement> retracedClasses =
+        retraceClassResult.stream().collect(Collectors.toList());
+    assertEquals(1, retracedClasses.size());
+    RetraceClassElement retraceClassElement = retracedClasses.get(0);
+    assertEquals(originalClass, retraceClassElement.getRetracedClass().getClassReference());
+    assertTrue(retraceClassElement.isCompilerSynthesized());
+    RetraceMethodResult retraceMethodResult = retraceClassResult.lookupMethod("<clinit>");
+    List<RetraceMethodElement> retracedMethods =
+        retraceMethodResult.stream().collect(Collectors.toList());
+    assertEquals(1, retracedMethods.size());
+    RetraceMethodElement retraceMethodElement = retracedMethods.get(0);
+    assertFalse(retraceMethodElement.isCompilerSynthesized());
+    assertTrue(retraceMethodElement.getRetracedMethod().isKnown());
+    assertEquals("<clinit>", retraceMethodElement.getRetracedMethod().getMethodName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 9c48052..6ebcaf7 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MapVersionWarningStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
+import com.android.tools.r8.retrace.stacktraces.MovedSynthetizedInfoStackTraceTest;
 import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleLinesNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleMapVersionsWarningStackTrace;
@@ -261,6 +262,11 @@
   }
 
   @Test
+  public void testMovedSynthetizedInfoStackTraceTest() throws Exception {
+    runRetraceTest(new MovedSynthetizedInfoStackTraceTest());
+  }
+
+  @Test
   public void testCircularReferenceStackTrace() throws Exception {
     // Proguard retrace (and therefore the default regular expression) will not retrace circular
     // reference exceptions.
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MovedSynthetizedInfoStackTraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MovedSynthetizedInfoStackTraceTest.java
new file mode 100644
index 0000000..75724b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MovedSynthetizedInfoStackTraceTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class MovedSynthetizedInfoStackTraceTest implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.lang.RuntimeException: foobar", "\tat foo.bar.inlinee$synthetic(BaseCommand.java:2)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
+        "com.android.tools.r8.BaseCommand$Builder -> foo.bar:",
+        "    1:1:void inlinee(java.util.Collection):0:0 -> inlinee$synthetic",
+        "    1:1:void inlinee$synthetic(java.util.Collection):0:0 -> inlinee$synthetic",
+        "    2:2:void inlinee(java.util.Collection):206:206 -> inlinee$synthetic",
+        "    2:2:void inlinee$synthetic(java.util.Collection):0:0 -> inlinee$synthetic",
+        "      # {\"id\":\"com.android.tools.r8.synthesized\"}",
+        "    4:4:void inlinee(java.util.Collection):208:208 -> inlinee$synthetic",
+        "    4:4:void inlinee$synthetic(java.util.Collection):0 -> inlinee$synthetic",
+        "    7:7:void error(origin.Origin,java.lang.Throwable):363:363 -> inlinee$synthetic",
+        "    7:7:void inlinee(java.util.Collection):210 -> inlinee$synthetic",
+        "    7:7:void inlinee$synthetic(java.util.Collection):0:0 -> inlinee$synthetic");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.lang.RuntimeException: foobar",
+        "\tat com.android.tools.r8.BaseCommand$Builder.inlinee(BaseCommand.java:206)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "java.lang.RuntimeException: foobar",
+        "\tat com.android.tools.r8.BaseCommand$Builder.void"
+            + " inlinee(java.util.Collection)(BaseCommand.java:206)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
new file mode 100644
index 0000000..c3fd851
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesR8SpecificTest.java
@@ -0,0 +1,330 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.SemanticVersion;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryProvidedProguardRulesR8SpecificTest
+    extends LibraryProvidedProguardRulesTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public LibraryType libraryType;
+
+  @Parameter(2)
+  public ProviderType providerType;
+
+  @Parameters(name = "{0}, AAR: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(),
+        ImmutableList.of(LibraryType.JAR_WITH_RULES),
+        ProviderType.values());
+  }
+
+  private static final String EXPECTED_A =
+      StringUtils.lines(
+          "-keep class A1 {", "  <init>();", "}", "-keep class A2 {", "  <init>();", "}");
+
+  private static final String EXPECTED_B =
+      StringUtils.lines(
+          "-keep class B1 {", "  <init>();", "}", "-keep class B2 {", "  <init>();", "}");
+
+  private static final String EXPECTED_C =
+      StringUtils.lines(
+          "-keep class C1 {", "  <init>();", "}", "-keep class C2 {", "  <init>();", "}");
+
+  private static final String EXPECTED_D =
+      StringUtils.lines(
+          "-keep class D1 {", "  <init>();", "}", "-keep class D2 {", "  <init>();", "}");
+
+  private static final String EXPECTED_E =
+      StringUtils.lines(
+          "-keep class E1 {", "  <init>();", "}", "-keep class E2 {", "  <init>();", "}");
+
+  private static final String EXPECTED_X =
+      StringUtils.lines(
+          "-keep class X1 {", "  <init>();", "}", "-keep class X2 {", "  <init>();", "}");
+
+  private Path buildLibrary() throws Exception {
+    ZipBuilder jarBuilder =
+        ZipBuilder.builder(temp.newFile(libraryType.isAar() ? "classes.jar" : "test.jar").toPath());
+    if (libraryType.hasRulesInJar()) {
+      jarBuilder.addText("META-INF/com.android.tools/r8/test1.pro", "-keep class A1");
+      jarBuilder.addText("META-INF/com.android.tools/r8/test2.pro", "-keep class A2");
+      jarBuilder.addText("META-INF/com.android.tools/r8-min-4.0.0/test1.pro", "-keep class B1");
+      jarBuilder.addText("META-INF/com.android.tools/r8-min-4.0.0/test2.pro", "-keep class B2");
+      jarBuilder.addText("META-INF/com.android.tools/r8-max-8.1.0/test1.pro", "-keep class C1");
+      jarBuilder.addText("META-INF/com.android.tools/r8-max-8.1.0/test2.pro", "-keep class C2");
+      jarBuilder.addText(
+          "META-INF/com.android.tools/r8-min-5.0.0-max-8.0.0/test1.pro", "-keep class D1");
+      jarBuilder.addText(
+          "META-INF/com.android.tools/r8-min-5.0.0-max-8.0.0/test2.pro", "-keep class D2");
+      jarBuilder.addText("META-INF/com.android.tools/r8-min-10.5.0/test1.pro", "-keep class E1");
+      jarBuilder.addText("META-INF/com.android.tools/r8-min-10.5.0/test2.pro", "-keep class E2");
+      jarBuilder.addText("META-INF/proguard/test1.pro", "-keep class X1");
+      jarBuilder.addText("META-INF/proguard/test2.pro", "-keep class X2");
+    }
+    if (libraryType.isAar()) {
+      // TODO(b/228319861): Also test AARs.
+      fail("Not tested");
+      return null;
+    } else {
+      return jarBuilder.build();
+    }
+  }
+
+  private Path buildLibraryProguardOnlyRules(String directory) throws Exception {
+    ZipBuilder jarBuilder =
+        ZipBuilder.builder(
+            temp.newFolder().toPath().resolve(libraryType.isAar() ? "classes.jar" : "test.jar"));
+    if (libraryType.hasRulesInJar()) {
+      jarBuilder.addText("META-INF/" + directory + "/test1.pro", "-keep class X1");
+      jarBuilder.addText("META-INF/" + directory + "/test2.pro", "-keep class X2");
+    }
+    if (libraryType.isAar()) {
+      // TODO(b/228319861): Also test AARs.
+      fail("Not tested");
+      return null;
+    } else {
+      return jarBuilder.build();
+    }
+  }
+
+  private Path buildLibraryR8VersionAgnosticOnlyRules() throws Exception {
+    ZipBuilder jarBuilder =
+        ZipBuilder.builder(
+            temp.newFolder().toPath().resolve(libraryType.isAar() ? "classes.jar" : "test.jar"));
+    if (libraryType.hasRulesInJar()) {
+      jarBuilder.addText("META-INF/com.android.tools/r8/test1.pro", "-keep class A1");
+      jarBuilder.addText("META-INF/com.android.tools/r8/test2.pro", "-keep class A2");
+    }
+    if (libraryType.isAar()) {
+      // TODO(b/228319861): Also test AARs.
+      fail("Not tested");
+      return null;
+    } else {
+      return jarBuilder.build();
+    }
+  }
+
+  private Path buildLibraryProguardOnlyRules() throws Exception {
+    return buildLibraryProguardOnlyRules("proguard");
+  }
+
+  private void runTest(SemanticVersion compilerVersion, String expected) throws Exception {
+    Path library = buildLibrary();
+    testForR8(Backend.DEX)
+        .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
+        .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
+        .setMinApi(AndroidApiLevel.B)
+        .setFakeCompilerVersion(compilerVersion)
+        .allowUnusedProguardConfigurationRules()
+        .compile()
+        .inspectProguardConfiguration(
+            configuration -> assertEquals(expected, configuration.toString()));
+  }
+
+  @Test
+  public void runTestVersion3() throws Exception {
+    runTest(
+        SemanticVersion.create(3, 0, 0), StringUtils.lines(EXPECTED_A.trim(), EXPECTED_C.trim()));
+  }
+
+  @Test
+  public void runTestVersion4() throws Exception {
+    runTest(
+        SemanticVersion.create(4, 0, 0),
+        StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim()));
+  }
+
+  @Test
+  public void runTestVersion5() throws Exception {
+    runTest(
+        SemanticVersion.create(5, 0, 0),
+        StringUtils.lines(
+            EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim(), EXPECTED_D.trim()));
+  }
+
+  @Test
+  public void runTestVersion8() throws Exception {
+    runTest(
+        SemanticVersion.create(8, 0, 0),
+        StringUtils.lines(
+            EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim(), EXPECTED_D.trim()));
+  }
+
+  @Test
+  public void runTestVersion8_1() throws Exception {
+    runTest(
+        SemanticVersion.create(8, 1, 0),
+        StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim()));
+  }
+
+  @Test
+  public void runTestVersion8_2() throws Exception {
+    runTest(
+        SemanticVersion.create(8, 2, 0), StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim()));
+  }
+
+  @Test
+  public void runTestVersion10() throws Exception {
+    runTest(
+        SemanticVersion.create(10, 0, 0), StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim()));
+  }
+
+  @Test
+  public void runTestVersion10_5() throws Exception {
+    runTest(
+        SemanticVersion.create(10, 5, 0),
+        StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_E.trim()));
+  }
+
+  @Test
+  public void runTestVersionMainR8VersionSpecificRules() throws Exception {
+    if (!Version.isMainVersion()) {
+      return;
+    }
+    Path library = buildLibrary();
+    testForR8(Backend.DEX)
+        .applyIf(
+            providerType == ProviderType.API,
+            b -> b.addProgramFiles(library).addProgramFiles(library))
+        .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
+        .setMinApi(AndroidApiLevel.B)
+        .allowUnusedProguardConfigurationRules()
+        .allowDiagnosticMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                assertEquals(
+                    1,
+                    diagnostics.getWarnings().stream()
+                        .filter(
+                            allOf(
+                                    diagnosticMessage(containsString("Running R8 version main")),
+                                    diagnosticMessage(containsString("Using version 8.1.0 for")))
+                                ::matches)
+                        .count()))
+        .inspectProguardConfiguration(
+            configuration ->
+                assertEquals(
+                    StringUtils.lines(EXPECTED_A.trim(), EXPECTED_B.trim(), EXPECTED_C.trim()),
+                    configuration.toString()));
+  }
+
+  @Test
+  public void runTestVersionMainR8VersionAgnosticOnlyRules() throws Exception {
+    if (!Version.isMainVersion()) {
+      return;
+    }
+    Path library = buildLibraryR8VersionAgnosticOnlyRules();
+    testForR8(Backend.DEX)
+        .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
+        .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
+        .setMinApi(AndroidApiLevel.B)
+        .allowUnusedProguardConfigurationRules()
+        .compile()
+        .inspectProguardConfiguration(
+            configuration -> assertEquals(EXPECTED_A, configuration.toString()));
+  }
+
+  @Test
+  public void testProguardOnlyRules() throws Exception {
+    Path library = buildLibraryProguardOnlyRules();
+    testForR8(Backend.DEX)
+        .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
+        .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
+        .setMinApi(AndroidApiLevel.B)
+        .setFakeCompilerVersion(SemanticVersion.create(1, 2, 3))
+        .allowUnusedProguardConfigurationRules()
+        .compile()
+        .inspectProguardConfiguration(
+            configuration -> assertEquals(EXPECTED_X, configuration.toString()));
+  }
+
+  @Test
+  public void testProguardOnlyRulesVersionMain() throws Exception {
+    Path library = buildLibraryProguardOnlyRules();
+    testForR8(Backend.DEX)
+        .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
+        .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
+        .setMinApi(AndroidApiLevel.B)
+        .allowUnusedProguardConfigurationRules()
+        .compile()
+        .inspectProguardConfiguration(
+            configuration -> assertEquals(EXPECTED_X, configuration.toString()));
+  }
+
+  @Test
+  public void testUnusedProguardOnlyRules() throws Exception {
+    for (String directory :
+        ImmutableList.of(
+            "proguard-min-6.1.0",
+            "proguard-max-7.0.0",
+            "proguard-min-6.1.0-max-7.0.0",
+            "proguard610",
+            "com.android.tools/proguard",
+            "com.android.tools/proguard-min-6.1.0",
+            "com.android.tools/proguard-max-7.0.0",
+            "com.android.tools/proguard-min-6.1.0-max-7.0.0",
+            "com.android.tools/proguard610")) {
+      Path library = buildLibraryProguardOnlyRules(directory);
+      testForR8(Backend.DEX)
+          .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
+          .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
+          .setMinApi(AndroidApiLevel.B)
+          .setFakeCompilerVersion(SemanticVersion.create(1, 2, 3))
+          .compile()
+          .inspectProguardConfiguration(
+              configuration -> assertEquals("", configuration.toString()));
+    }
+  }
+
+  @Test
+  public void testUnusedProguardOnlyRulesVersionMain() throws Exception {
+    for (String directory :
+        ImmutableList.of(
+            "proguard-min-6.1.0",
+            "proguard-max-7.0.0",
+            "proguard-min-6.1.0-max-7.0.0",
+            "proguard610",
+            "com.android.tools/proguard",
+            "com.android.tools/proguard-min-6.1.0",
+            "com.android.tools/proguard-max-7.0.0",
+            "com.android.tools/proguard-min-6.1.0-max-7.0.0",
+            "com.android.tools/proguard610")) {
+      Path library = buildLibraryProguardOnlyRules(directory);
+      testForR8(Backend.DEX)
+          .applyIf(providerType == ProviderType.API, b -> b.addProgramFiles(library))
+          .applyIf(providerType == ProviderType.INJARS, b -> b.addKeepRules("-injars " + library))
+          .setMinApi(AndroidApiLevel.B)
+          .compile()
+          .inspectProguardConfiguration(
+              configuration -> assertEquals("", configuration.toString()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
index 2741404..6472a03 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
@@ -47,7 +46,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class LibraryProvidedProguardRulesTest extends TestBase {
+public class LibraryProvidedProguardRulesTest extends LibraryProvidedProguardRulesTestBase {
 
   static class A {
     private static String buildClassName(String className) {
@@ -66,30 +65,6 @@
 
   static class B {}
 
-  enum LibraryType {
-    JAR_WITH_RULES,
-    AAR_WITH_RULES,
-    AAR_WITH_RULES_ONLY_IN_JAR,
-    AAR_WITH_RULES_BOTH_IN_JAR_AND_IN_AAR;
-
-    boolean isAar() {
-      return this != JAR_WITH_RULES;
-    }
-
-    boolean hasRulesInJar() {
-      return this != AAR_WITH_RULES;
-    }
-
-    boolean hasRulesInAar() {
-      return this == AAR_WITH_RULES || this == AAR_WITH_RULES_BOTH_IN_JAR_AND_IN_AAR;
-    }
-  }
-
-  enum ProviderType {
-    API,
-    INJARS
-  }
-
   @Parameter(0)
   public TestParameters parameters;
 
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTestBase.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTestBase.java
new file mode 100644
index 0000000..4bca489
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTestBase.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.TestBase;
+
+public class LibraryProvidedProguardRulesTestBase extends TestBase {
+
+  enum LibraryType {
+    JAR_WITH_RULES,
+    AAR_WITH_RULES,
+    AAR_WITH_RULES_ONLY_IN_JAR,
+    AAR_WITH_RULES_BOTH_IN_JAR_AND_IN_AAR;
+
+    boolean isAar() {
+      return this != JAR_WITH_RULES;
+    }
+
+    boolean hasRulesInJar() {
+      return this != AAR_WITH_RULES;
+    }
+
+    boolean hasRulesInAar() {
+      return this == AAR_WITH_RULES || this == AAR_WITH_RULES_BOTH_IN_JAR_AND_IN_AAR;
+    }
+  }
+
+  enum ProviderType {
+    API,
+    INJARS
+  }
+}
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 d7df00b..478af32 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -13,6 +13,7 @@
 import static com.android.tools.r8.synthesis.SyntheticNaming.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
 import static org.hamcrest.CoreMatchers.containsString;
 
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
 import com.android.tools.r8.references.ClassReference;
@@ -130,6 +131,14 @@
     return syntheticClass(classReference, naming.BACKPORT_WITH_FORWARDING, id);
   }
 
+  public static ClassReference syntheticRecordTagClass() {
+    return Reference.classFromDescriptor(DexItemFactory.recordTagDescriptorString);
+  }
+
+  public static ClassReference syntheticRecordHelperClass(ClassReference reference, int id) {
+    return syntheticClass(reference, naming.RECORD_HELPER, id);
+  }
+
   public static ClassReference syntheticTwrCloseResourceClass(Class<?> clazz, int id) {
     return syntheticClass(clazz, naming.TWR_CLOSE_RESOURCE, id);
   }
@@ -155,10 +164,13 @@
   }
 
   public static MethodReference syntheticNestInstanceFieldGetter(Field field) {
-    FieldReference fieldReference = Reference.fieldFromField(field);
+    return syntheticNestInstanceFieldGetter(Reference.fieldFromField(field));
+  }
+
+  public static MethodReference syntheticNestInstanceFieldGetter(FieldReference fieldReference) {
     return Reference.method(
         fieldReference.getHolderClass(),
-        NEST_ACCESS_FIELD_GET_NAME_PREFIX + field.getName(),
+        NEST_ACCESS_FIELD_GET_NAME_PREFIX + fieldReference.getFieldName(),
         Collections.emptyList(),
         fieldReference.getFieldType());
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index ba1898d..7596ee6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -130,6 +130,11 @@
   }
 
   @Override
+  public TypeSubject getSuperType() {
+    throw new Unreachable("Absent class has no super type");
+  }
+
+  @Override
   public boolean isInterface() {
     throw new Unreachable("Cannot determine if an absent class is an interface");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 58dbf1d..1aebbdd 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -187,6 +187,8 @@
   @Override
   public abstract ClassAccessFlags getAccessFlags();
 
+  public abstract TypeSubject getSuperType();
+
   public abstract boolean isInterface();
 
   public abstract boolean isAbstract();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index b973782..81c7400 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -159,10 +161,15 @@
   }
 
   public CodeInspector(AndroidApp app, String proguardMapContent) throws IOException {
+    this(app, proguardMapContent, emptyConsumer());
+  }
+
+  public CodeInspector(
+      AndroidApp app, String proguardMapContent, Consumer<InternalOptions> optionsConsumer)
+      throws IOException {
     this(
-        new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
-            .read(
-                StringResource.fromString(proguardMapContent, Origin.unknown())));
+        new ApplicationReader(app, runOptionsConsumer(optionsConsumer), Timing.empty())
+            .read(StringResource.fromString(proguardMapContent, Origin.unknown())));
   }
 
   public CodeInspector(DexApplication application) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 1f9b372..d711ac1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
@@ -12,6 +15,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -179,14 +183,21 @@
   }
 
   public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
-    if (!targetSubject.isPresent()) {
-      throw new IllegalArgumentException();
-    }
-    return invokesMethod(targetSubject.getFinalReference());
+    return invokesMethod(
+        () -> {
+          assertThat(targetSubject, isPresent());
+          return targetSubject.getFinalReference();
+        });
   }
 
   public static Matcher<MethodSubject> invokesMethod(MethodReference targetReference) {
+    return invokesMethod(() -> targetReference);
+  }
+
+  public static Matcher<MethodSubject> invokesMethod(
+      Supplier<MethodReference> targetReferenceSupplier) {
     return new TypeSafeMatcher<MethodSubject>() {
+
       @Override
       protected boolean matchesSafely(MethodSubject subject) {
         if (!subject.isPresent()) {
@@ -195,11 +206,13 @@
         if (!subject.getMethod().hasCode()) {
           return false;
         }
+        MethodReference targetReference = targetReferenceSupplier.get();
         return subject.streamInstructions().anyMatch(isInvokeWithTarget(targetReference));
       }
 
       @Override
       public void describeTo(Description description) {
+        MethodReference targetReference = targetReferenceSupplier.get();
         description.appendText(
             "invokes method `" + MethodReferenceUtils.toSourceString(targetReference) + "`");
       }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 738507a..fcfe160 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -388,6 +388,11 @@
   }
 
   @Override
+  public TypeSubject getSuperType() {
+    return new TypeSubject(codeInspector, dexClass.getSuperType());
+  }
+
+  @Override
   public String getOriginalName() {
     if (getNaming() != null) {
       return getNaming().originalName;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 4a3d5f9..82eaa9d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import static org.hamcrest.CoreMatchers.anything;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -846,6 +847,14 @@
     }
   }
 
+  @SuppressWarnings("unchecked")
+  public static <T> Matcher<T> ifThen(boolean condition, Matcher<T> matcher) {
+    if (condition) {
+      return matcher;
+    }
+    return (Matcher<T>) anything();
+  }
+
   public static <T> Matcher<T> onlyIf(boolean condition, Matcher<T> matcher) {
     return notIf(matcher, !condition);
   }
diff --git a/third_party/android_jar/lib-v34.tar.gz.sha1 b/third_party/android_jar/lib-v34.tar.gz.sha1
new file mode 100644
index 0000000..3b7b739
--- /dev/null
+++ b/third_party/android_jar/lib-v34.tar.gz.sha1
@@ -0,0 +1 @@
+ac28074fa7b977e03eb0692248e1edcee36a2c33
\ No newline at end of file
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index 275471b..9883fe5 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-f081c538df68649432fa8e45ec511d43d5548396
\ No newline at end of file
+29b5c8dfdccf33e7a540d5de29476805a7d5c2f0
\ No newline at end of file
diff --git a/tools/r8_release.py b/tools/r8_release.py
index c799d90..f98e24f 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -325,6 +325,17 @@
       sources.write(re.sub(pattern, replace, line))
 
 
+def replace_startswith(prefix, replacement, path):
+  with open(path, "r") as source:
+    lines = source.readlines()
+  with open(path, "w") as source:
+    for line in lines:
+      if line.startswith(prefix):
+        source.write(replacement)
+      else:
+        source.write(line)
+
+
 def download_file(version, file, dst):
   dir = 'raw' if len(version) != 40 else 'raw/main'
   urllib.request.urlretrieve(
@@ -849,6 +860,12 @@
           "R8_DEV_BRANCH = '%s.%s" % (str(semver.major), str(semver.minor)),
           THIS_FILE_RELATIVE)
 
+        # Update main version file with the new dev branch.
+        replace_startswith(
+          '  public static final String ACTIVE_DEV_VERSION = ',
+          '  public static final String ACTIVE_DEV_VERSION = "' + branch_version + '.0"',
+          R8_VERSION_FILE)
+
         message = \
             'Prepare %s for branch %s' % (THIS_FILE_RELATIVE, branch_version)
         subprocess.check_call(['git', 'commit', '-a', '-m', message])
diff --git a/tools/test.py b/tools/test.py
index a4e7b6e..e7e2b99 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -78,6 +78,10 @@
   result.add_option('--all-tests', '--all_tests',
       help='Run tests in all configurations.',
       default=False, action='store_true')
+  result.add_option('--art-profile-rewriting-completeness-check',
+       '--art_profile_rewriting_completeness_check',
+      help='Enable completeness check for ART profile rewriting.',
+      default=False, action='store_true')
   result.add_option('--slow-tests', '--slow_tests',
       help='Also run slow tests.',
       default=False, action='store_true')
@@ -287,6 +291,8 @@
     gradle_args.append('-Ponly_internal')
   if options.all_tests:
     gradle_args.append('-Pall_tests')
+  if options.art_profile_rewriting_completeness_check:
+    gradle_args.append('-Part_profile_rewriting_completeness_check=1')
   if options.slow_tests:
     gradle_args.append('-Pslow_tests=1')
   if options.tool:
diff --git a/tools/utils.py b/tools/utils.py
index c5fb4ce..5cb2cbe 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -181,7 +181,7 @@
     assert os.path.exists(build_tools_dir)
     return build_tools_dir
   else:
-    versions = ['30.0.3', '30.0.2', '30.0.1', '30.0.0']
+    versions = ['33.0.1', '32.0.0']
     for version in versions:
       build_tools_dir = os.path.join(getAndroidHome(), 'build-tools', version)
       if os.path.exists(build_tools_dir):