Merge commit '9cd6f525f542275e9bb588855e16364fbfff1c26' into dev-release
diff --git a/Regress78493232.jar b/Regress78493232.jar
new file mode 100644
index 0000000..6dee6f0
--- /dev/null
+++ b/Regress78493232.jar
Binary files differ
diff --git a/build.gradle b/build.gradle
index 5b9c8df..a138d84 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,9 +2,10 @@
 // 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.
 
+
+import desugaredlibrary.CustomConversionAsmRewriterTask
 import dx.DexMergerTask
 import dx.DxTask
-import desugaredlibrary.CustomConversionAsmRewriterTask
 import net.ltgt.gradle.errorprone.CheckSeverity
 import org.gradle.internal.os.OperatingSystem
 import smali.SmaliTask
@@ -79,6 +80,14 @@
             srcDirs "third_party/api_database/api_database"
         }
     }
+    main17 {
+        java {
+            srcDirs = ['src/main/java']
+        }
+        resources {
+            srcDirs "third_party/api_database/api_database"
+        }
+    }
     test {
         java {
             srcDirs = [
@@ -252,6 +261,25 @@
     main11Implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
     main11Implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
 
+    main17Implementation "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
+    main17Implementation "com.google.code.gson:gson:$gsonVersion"
+    // Include all of guava when compiling the code, but exclude annotations that we don't
+    // need from the packaging.
+    main17CompileOnly("com.google.guava:guava:$guavaVersion")
+    main17Implementation("com.google.guava:guava:$guavaVersion", {
+        exclude group: 'com.google.errorprone'
+        exclude group: 'com.google.code.findbugs'
+        exclude group: 'com.google.j2objc'
+        exclude group: 'org.codehaus.mojo'
+    })
+    main17Implementation group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+    main17Implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
+    main17Implementation group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    main17Implementation group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+    main17Implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+    main17Implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+    main17Implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
+    
     examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
 
     testCompile sourceSets.examples.output
@@ -630,6 +658,11 @@
         JavaVersion.VERSION_11,
         false)
 setJdkCompilationWithCompatibility(
+        sourceSets.main17.compileJavaTaskName,
+        'jdk-17',
+        JavaVersion.VERSION_17,
+        false)
+setJdkCompilationWithCompatibility(
         sourceSets.examplesJava11.compileJavaTaskName,
         'jdk-11',
         JavaVersion.VERSION_11,
@@ -782,6 +815,11 @@
     archiveFileName = 'sources_main_11.jar'
 }
 
+task repackageSources17(type: Jar) {
+    from sourceSets.main17.output
+    archiveFileName = 'sources_main_17.jar'
+}
+
 def r8CreateTask(name, baseName, sources, includeLibraryLicenses, includeSwissArmyKnife) {
     return tasks.create("r8Create${name}", Jar) {
         entryCompression ZipEntryCompression.STORED
@@ -872,6 +910,20 @@
     outputs.files r8Task.outputs.files
 }
 
+task r8WithDeps17 {
+    dependsOn repackageSources17
+    dependsOn repackageDeps
+    inputs.files ([repackageSources17.outputs, repackageDeps.outputs])
+    def r8Task = r8CreateTask(
+            'WithDeps17',
+            'r8_with_deps_17.jar',
+            repackageSources17.outputs.files + repackageDeps.outputs.files,
+            true,
+            true)
+    dependsOn r8Task
+    outputs.files r8Task.outputs.files
+}
+
 task r8WithRelocatedDeps {
     def output = "${buildDir}/libs/r8_with_relocated_deps.jar"
     dependsOn r8RelocateTask(r8WithDeps, output)
@@ -886,6 +938,13 @@
     outputs.file output
 }
 
+task r8WithRelocatedDeps17 {
+    def output = "${buildDir}/libs/r8_with_relocated_deps_17.jar"
+    dependsOn r8RelocateTask(r8WithDeps17, output)
+    inputs.files r8WithDeps17.outputs.files
+    outputs.file output
+}
+
 task r8WithoutDeps {
     dependsOn repackageSources
     inputs.files repackageSources.outputs
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index ad3bf13..a94d834 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -4,27 +4,11 @@
 
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.dex.ApplicationWriter;
-import com.android.tools.r8.dex.Marker;
-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.DexProgramClass;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
-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.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 
 public class DexFileMergerHelper {
 
@@ -57,63 +41,26 @@
     return aIndex <= bIndex ? a.get() : b.get();
   }
 
+  // NOTE: Don't change this signature! Reflectively accessed from bazel DexFileMerger.
   public static void run(
       D8Command command, Boolean minimalMainDex, Map<String, Integer> inputOrdering)
       throws CompilationFailedException {
     InternalOptions options = command.getInternalOptions();
-    ExceptionUtils.withD8CompilationHandler(
-        options.reporter,
-        () -> runInternal(command.getInputApp(), options, minimalMainDex, inputOrdering));
-  }
 
-  private static void runInternal(
-      AndroidApp inputApp,
-      InternalOptions options,
-      Boolean minimalMainDex,
-      Map<String, Integer> inputOrdering)
-      throws IOException {
+    // TODO(b/241063980): Move this to D8Command.Builder.setDisableDesugaring(true) in bazel.
     options.desugarState = DesugarState.OFF;
+
+    // TODO(b/241063980): Is this configuration needed?
     options.enableMainDexListCheck = false;
+
+    // TODO(b/241063980): Is this configuration needed?
     options.minimalMainDex = minimalMainDex;
-    assert !options.isMinifying();
-    options.inlinerOptions().enableInlining = false;
-    options.outline.enabled = false;
 
-    ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
-    try {
-      try {
-        Timing timing = new Timing("DexFileMerger");
-        ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
-        DexApplication app =
-            applicationReader.read(
-                null,
-                executor,
-                new DexFileMergerHelper(inputOrdering)::keepFirstProgramClassConflictResolver);
+    // TODO(b/241063980): Add API to configure this in D8Command.Builder.
+    options.programClassConflictResolver =
+        new DexFileMergerHelper(inputOrdering)::keepFirstProgramClassConflictResolver;
 
-        AppView<AppInfo> appView =
-            AppView.createForD8(
-                AppInfo.createInitialAppInfo(
-                    app,
-                    GlobalSyntheticsStrategy.forNonSynthesizing(),
-                    applicationReader.readMainDexClasses(app)));
-
-        D8.optimize(appView, options, timing, executor);
-
-        List<Marker> markers = appView.dexItemFactory().extractMarkers();
-
-        assert !options.hasMethodsFilter();
-        ApplicationWriter writer = new ApplicationWriter(appView, markers);
-        writer.write(executor);
-        options.printWarnings();
-      } catch (ExecutionException e) {
-        throw unwrapExecutionException(e);
-      } finally {
-        inputApp.signalFinishedToProviders(options.reporter);
-        options.signalFinishedToConsumers();
-      }
-    } finally {
-      executor.shutdown();
-    }
+    D8.runForTesting(command.getInputApp(), options);
   }
 
   public static void runD8ForTesting(D8Command command, boolean dontCreateMarkerInD8)
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
index 6858fe9..d3f035b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
@@ -145,17 +145,10 @@
 
   public AssignabilityResult isLocalsAssignable(
       Int2ObjectSortedMap<FrameType> sourceLocals, Int2ObjectSortedMap<FrameType> targetLocals) {
-    // TODO(b/229826687): The tail of locals could have top(s) at destination but still be valid.
     int localsLastKey = sourceLocals.isEmpty() ? -1 : sourceLocals.lastIntKey();
     int otherLocalsLastKey = targetLocals.isEmpty() ? -1 : targetLocals.lastIntKey();
-    if (localsLastKey < otherLocalsLastKey) {
-      return new FailedAssignabilityResult(
-          "Source locals "
-              + MapUtils.toString(sourceLocals)
-              + " have different local indices than "
-              + MapUtils.toString(targetLocals));
-    }
-    for (int i = 0; i < otherLocalsLastKey; i++) {
+    int maxKey = Math.max(localsLastKey, otherLocalsLastKey);
+    for (int i = 0; i <= maxKey; i++) {
       FrameType sourceType =
           sourceLocals.containsKey(i) ? sourceLocals.get(i) : FrameType.oneWord();
       FrameType destinationType =
@@ -164,23 +157,33 @@
         destinationType = FrameType.twoWord();
       }
       if (!isFrameTypeAssignable(sourceType, destinationType)) {
-        return new FailedAssignabilityResult(
-            "Could not assign '"
-                + MapUtils.toString(sourceLocals)
-                + "' to '"
-                + MapUtils.toString(targetLocals)
-                + "'. The local at index "
-                + i
-                + " with '"
-                + sourceType
-                + "' not being assignable to '"
-                + destinationType
-                + "'");
+        return reportFailedAssignabilityResult(
+            sourceLocals, targetLocals, sourceType, destinationType, i);
       }
     }
     return new SuccessfulAssignabilityResult();
   }
 
+  private FailedAssignabilityResult reportFailedAssignabilityResult(
+      Int2ObjectSortedMap<FrameType> sourceLocals,
+      Int2ObjectSortedMap<FrameType> targetLocals,
+      FrameType sourceType,
+      FrameType destinationType,
+      int index) {
+    return new FailedAssignabilityResult(
+        "Could not assign '"
+            + MapUtils.toString(sourceLocals)
+            + "' to '"
+            + MapUtils.toString(targetLocals)
+            + "'. The local at index "
+            + index
+            + " with '"
+            + sourceType
+            + "' not being assignable to '"
+            + destinationType
+            + "'");
+  }
+
   public AssignabilityResult isStackAssignable(
       Deque<PreciseFrameType> sourceStack, Deque<PreciseFrameType> targetStack) {
     if (sourceStack.size() != targetStack.size()) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
index bdd6512..09cabd8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -107,15 +107,17 @@
     for (int i = 0; i < code.getInstructions().size(); i++) {
       CfInstruction instruction = code.getInstruction(i);
       assert !state.isError();
-      // Check the exceptional edge prior to evaluating the instruction. The local state is stable
-      // at this point as store operations are not throwing and the current stack does not
-      // affect the exceptional transfer (the exception edge is always a singleton stack).
-      if (instruction.canThrow()) {
-        assert !instruction.isStore();
-        state = checkExceptionEdges(state, labelToFrameMap);
-      }
       if (instruction.isLabel()) {
         updateActiveCatchHandlers(instruction.asLabel());
+      } else {
+        // The ExceptionStackFrame is defined as the current frame having an empty operand stack.
+        // All instructions, not only throwing instructions, check the exception frame to be
+        // assignable to all exception edges.
+        // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.9
+        if (appView.options().enableCheckAllInstructionsDuringStackMapVerification
+            || instruction.canThrow()) {
+          state = checkExceptionEdges(state, labelToFrameMap);
+        }
       }
       eventConsumer.acceptInstructionState(instruction, state);
       state = instruction.evaluate(state, appView, config);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 285ebe5..249de08 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
 import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -111,12 +112,27 @@
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     Invoke.Type invokeType = Invoke.Type.fromCfOpcode(opcode, method, context, appView);
-    MethodLookupResult lookup = graphLens.lookupMethod(method, context.getReference(), invokeType);
-    DexMethod rewrittenMethod = lookup.getReference();
-    String owner = namingLens.lookupInternalName(rewrittenMethod.holder);
-    String name = namingLens.lookupName(rewrittenMethod).toString();
-    String desc = rewrittenMethod.proto.toDescriptorString(namingLens);
-    visitor.visitMethodInsn(lookup.getType().getCfOpcode(), owner, name, desc, itf);
+    if (invokeType == Type.POLYMORPHIC) {
+      assert dexItemFactory.polymorphicMethods.isPolymorphicInvoke(method);
+      // The method is one of java.lang.MethodHandle.invoke/invokeExact.
+      // Only the method signature (getProto()) is to be type rewritten.
+      DexProto rewrittenProto = rewriter.rewriteProto(method.getProto());
+      visitor.visitMethodInsn(
+          invokeType.getCfOpcode(),
+          DescriptorUtils.descriptorToInternalName(method.holder.toDescriptorString()),
+          method.getName().toString(),
+          rewrittenProto.toDescriptorString(namingLens),
+          itf);
+    } else {
+      MethodLookupResult lookup =
+          graphLens.lookupMethod(method, context.getReference(), invokeType);
+      Invoke.Type rewrittenType = lookup.getType();
+      DexMethod rewrittenMethod = lookup.getReference();
+      String owner = namingLens.lookupInternalName(rewrittenMethod.holder);
+      String name = namingLens.lookupName(rewrittenMethod).toString();
+      String desc = rewrittenMethod.proto.toDescriptorString(namingLens);
+      visitor.visitMethodInsn(rewrittenType.getCfOpcode(), owner, name, desc, itf);
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index e351e57..31967b1 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -46,7 +46,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LibraryClassCollection;
 import com.android.tools.r8.utils.MainDexListParser;
-import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -98,46 +97,25 @@
     }
   }
 
-  public final LazyLoadedDexApplication read(
-      ExecutorService executorService)
-      throws IOException {
-    return read(
-        inputApp.getProguardMapInputData(),
-        executorService,
-        ProgramClassCollection.defaultConflictResolver(options.reporter));
+  public final LazyLoadedDexApplication read(ExecutorService executorService) throws IOException {
+    return read(inputApp.getProguardMapInputData(), executorService);
   }
 
   public final LazyLoadedDexApplication readWithoutDumping(ExecutorService executorService)
       throws IOException {
-    return read(
-        inputApp.getProguardMapInputData(),
-        executorService,
-        ProgramClassCollection.defaultConflictResolver(options.reporter),
-        DumpInputFlags.noDump());
+    return read(inputApp.getProguardMapInputData(), executorService, DumpInputFlags.noDump());
   }
 
   public final LazyLoadedDexApplication read(
       StringResource proguardMap,
       ExecutorService executorService)
       throws IOException {
-    return read(
-        proguardMap,
-        executorService,
-        ProgramClassCollection.defaultConflictResolver(options.reporter));
+    return read(proguardMap, executorService, options.getDumpInputFlags());
   }
 
   public final LazyLoadedDexApplication read(
       StringResource proguardMap,
       ExecutorService executorService,
-      ProgramClassConflictResolver resolver)
-      throws IOException {
-    return read(proguardMap, executorService, resolver, options.getDumpInputFlags());
-  }
-
-  public final LazyLoadedDexApplication read(
-      StringResource proguardMap,
-      ExecutorService executorService,
-      ProgramClassConflictResolver resolver,
       DumpInputFlags dumpInputFlags)
       throws IOException {
     assert verifyMainDexOptionsCompatible(inputApp, options);
@@ -148,8 +126,7 @@
     }
 
     timing.begin("DexApplication.read");
-    final LazyLoadedDexApplication.Builder builder =
-        DexApplication.builder(options, timing, resolver);
+    final LazyLoadedDexApplication.Builder builder = DexApplication.builder(options, timing);
     try {
       List<Future<?>> futures = new ArrayList<>();
       // Still preload some of the classes, primarily for two reasons:
diff --git a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
index c71d0d3..b297a2b 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.dex.FileWriter.MixedSectionOffsets;
 import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupItem;
+import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -13,7 +15,6 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedArray;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
@@ -71,6 +73,7 @@
     collectStartupItems(virtualFile);
   }
 
+  /** This adds all startup items to the startup layouts (i.e., the fields of this class). */
   private void collectStartupItems(VirtualFile virtualFile) {
     Map<DexType, DexProgramClass> virtualFileDefinitions =
         MapUtils.newIdentityHashMap(
@@ -79,11 +82,55 @@
             virtualFile.classes().size());
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView, true);
     StartupIndexedItemCollection indexedItemCollection = new StartupIndexedItemCollection();
-    for (StartupClass<DexType, DexMethod> startupClass : startupOrderForWriting.getClasses()) {
-      assert !startupClass.isSynthetic();
-      DexProgramClass definition = virtualFileDefinitions.get(startupClass.getReference());
-      if (definition != null) {
-        definition.collectIndexedItems(appView, indexedItemCollection, rewriter);
+    for (StartupItem<DexType, DexMethod, ?> startupItem : startupOrderForWriting.getItems()) {
+      // All synthetic startup items should be removed after calling
+      // StartupOrder#toStartupOrderForWriting.
+      assert !startupItem.isSynthetic();
+      startupItem.accept(
+          startupClass ->
+              collectStartupItems(startupClass, indexedItemCollection, virtualFileDefinitions),
+          startupMethod ->
+              collectStartupItems(
+                  startupMethod, indexedItemCollection, virtualFileDefinitions, rewriter));
+    }
+  }
+
+  private void collectStartupItems(
+      StartupClass<DexType, DexMethod> startupClass,
+      StartupIndexedItemCollection indexedItemCollection,
+      Map<DexType, DexProgramClass> virtualFileDefinitions) {
+    DexProgramClass definition = virtualFileDefinitions.get(startupClass.getReference());
+    if (definition != null) {
+      // Note that this must not call definition.collectIndexedItems, since that would collect all
+      // items from the class, and not only the startup items.
+      indexedItemCollection.addClass(definition);
+
+      // Collect the descriptor of the current type.
+      definition.getType().collectIndexedItems(appView, indexedItemCollection);
+
+      // Collect the descriptors (strings) of the supertypes.
+      definition.forEachImmediateSupertype(
+          supertype -> supertype.collectIndexedItems(appView, indexedItemCollection));
+
+      // TODO(b/238173796): Consider collecting the source file, the annotations, the enclosing
+      //  method attribute, the inner class attribute, and the fields (i.e., annotations and static
+      //  values).
+    }
+  }
+
+  private void collectStartupItems(
+      StartupMethod<DexType, DexMethod> startupMethod,
+      StartupIndexedItemCollection indexedItemCollection,
+      Map<DexType, DexProgramClass> virtualFileDefinitions,
+      LensCodeRewriterUtils rewriter) {
+    DexMethod methodReference = startupMethod.getReference();
+    DexProgramClass holder = virtualFileDefinitions.get(methodReference.getHolderType());
+    ProgramMethod method = methodReference.lookupOnProgramClass(holder);
+    if (method != null) {
+      methodReference.collectIndexedItems(appView, indexedItemCollection);
+      if (indexedItemCollection.addCode(method)) {
+        DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
+        code.collectIndexedItems(appView, indexedItemCollection, method, rewriter);
       }
     }
   }
@@ -163,7 +210,6 @@
         classDataLayout.add(clazz);
       }
       addTypeList(clazz.getInterfaces());
-      clazz.forEachProgramMethodMatching(DexEncodedMethod::hasCode, codeLayout::add);
       DexAnnotationDirectory annotationDirectory =
           mixedSectionOffsets.getAnnotationDirectoryForClass(clazz);
       if (annotationDirectory != null) {
@@ -178,6 +224,14 @@
       return true;
     }
 
+    public boolean addCode(ProgramMethod method) {
+      if (method.getDefinition().hasCode()) {
+        codeLayout.add(method);
+        return true;
+      }
+      return false;
+    }
+
     @Override
     public boolean addField(DexField field) {
       return true;
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 e658265..61a0257 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
@@ -122,14 +122,17 @@
       GraphLens graphLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    MethodLookupResult lookup =
-        graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
-    assert lookup.getType() == Type.POLYMORPHIC;
-    writeFirst(A, G, dest);
-    write16BitReference(lookup.getReference(), dest, mapping);
-    write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
-
+    // The method is one of java.lang.MethodHandle.invoke/invokeExact.
+    // Only the method signature (getProto()) is to be type rewritten.
+    assert rewriter.dexItemFactory().polymorphicMethods.isPolymorphicInvoke(getMethod());
+    assert getMethod()
+        == graphLens
+            .lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC)
+            .getReference();
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
+    writeFirst(A, G, dest);
+    write16BitReference(getMethod(), dest, mapping);
+    write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
     write16BitReference(rewrittenProto, dest, mapping);
   }
 
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 ace2725..b795316 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
@@ -59,14 +59,17 @@
       GraphLens graphLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    MethodLookupResult lookup =
-        graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
-    assert lookup.getType() == Type.POLYMORPHIC;
-    writeFirst(AA, dest);
-    write16BitReference(lookup.getReference(), dest, mapping);
-    write16BitValue(CCCC, dest);
-
+    // The method is one of java.lang.MethodHandle.invoke/invokeExact.
+    // Only the method signature (getProto()) is to be type rewritten.
+    assert rewriter.dexItemFactory().polymorphicMethods.isPolymorphicInvoke(getMethod());
+    assert getMethod()
+        == graphLens
+            .lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC)
+            .getReference();
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
+    writeFirst(AA, dest);
+    write16BitReference(getMethod(), dest, mapping);
+    write16BitValue(CCCC, dest);
     write16BitReference(rewrittenProto, dest, mapping);
   }
 
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
index 7bbaba0..d777790 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
@@ -23,7 +23,7 @@
   }
 
   @Override
-  public Collection<StartupClass<DexType, DexMethod>> getClasses() {
+  public Collection<StartupItem<DexType, DexMethod, ?>> getItems() {
     return Collections.emptyList();
   }
 
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
index 4122aa0..4b749f3 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
@@ -26,20 +27,23 @@
 
 public class NonEmptyStartupOrder extends StartupOrder {
 
-  private final LinkedHashSet<StartupClass<DexType, DexMethod>> startupClasses;
+  private final LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems;
 
-  // Redundant sets to allow efficient querying without boxing.
+  // Sets to allow efficient querying without boxing.
   private final Set<DexType> nonSyntheticStartupClasses = Sets.newIdentityHashSet();
   private final Set<DexType> syntheticStartupClasses = Sets.newIdentityHashSet();
 
-  NonEmptyStartupOrder(LinkedHashSet<StartupClass<DexType, DexMethod>> startupClasses) {
-    assert !startupClasses.isEmpty();
-    this.startupClasses = startupClasses;
-    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
-      if (startupClass.isSynthetic()) {
-        syntheticStartupClasses.add(startupClass.getReference());
+  NonEmptyStartupOrder(LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems) {
+    assert !startupItems.isEmpty();
+    this.startupItems = startupItems;
+    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
+      if (startupItem.isSynthetic()) {
+        assert startupItem.isStartupClass();
+        syntheticStartupClasses.add(startupItem.asStartupClass().getReference());
       } else {
-        nonSyntheticStartupClasses.add(startupClass.getReference());
+        DexReference reference =
+            startupItem.apply(StartupClass::getReference, StartupMethod::getReference);
+        nonSyntheticStartupClasses.add(reference.getContextType());
       }
     }
   }
@@ -67,8 +71,8 @@
   }
 
   @Override
-  public Collection<StartupClass<DexType, DexMethod>> getClasses() {
-    return startupClasses;
+  public Collection<StartupItem<DexType, DexMethod, ?>> getItems() {
+    return startupItems;
   }
 
   @Override
@@ -78,22 +82,66 @@
 
   @Override
   public StartupOrder rewrittenWithLens(GraphLens graphLens) {
-    LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses =
-        new LinkedHashSet<>(startupClasses.size());
-    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
-      rewrittenStartupClasses.add(
-          StartupClass.dexBuilder()
-              .setFlags(startupClass.getFlags())
-              .setClassReference(graphLens.lookupType(startupClass.getReference()))
-              .build());
+    LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
+        new LinkedHashSet<>(startupItems.size());
+    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
+      if (startupItem.isStartupClass()) {
+        StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
+        rewrittenStartupItems.add(
+            StartupClass.dexBuilder()
+                .setClassReference(graphLens.lookupType(startupClass.getReference()))
+                .setSynthetic(startupItem.isSynthetic())
+                .build());
+      } else {
+        assert !startupItem.isSynthetic();
+        StartupMethod<DexType, DexMethod> startupMethod = startupItem.asStartupMethod();
+        // TODO(b/238173796): This should account for one-to-many mappings. e.g., when a bridge is
+        //  created.
+        rewrittenStartupItems.add(
+            StartupMethod.dexBuilder()
+                .setMethodReference(
+                    graphLens.getRenamedMethodSignature(startupMethod.getReference()))
+                .build());
+      }
     }
-    return createNonEmpty(rewrittenStartupClasses);
+    return createNonEmpty(rewrittenStartupItems);
   }
 
+  /**
+   * This is called to process the startup order before computing the startup layouts.
+   *
+   * <p>This processing makes two key changes to the startup order:
+   *
+   * <ul>
+   *   <li>Synthetic startup classes on the form "SLcom/example/SyntheticContext;" represents that
+   *       any method of any synthetic class that have been synthesized from SyntheticContext has
+   *       been executed. This pass removes such entries from the startup order, and replaces them
+   *       by all the methods from all of the synthetics that have been synthesized from
+   *       SyntheticContext.
+   *   <li>Moreover, this inserts a StartupClass event for all supertypes of a given class next to
+   *       the class in the startup order. This ensures that the classes from the super hierarchy
+   *       will be laid out close to their subclasses, at the point where the subclasses are used
+   *       during startup.
+   *       <p>Note that this normally follows from the trace already, except that the class
+   *       initializers of interfaces are not executed when a subclass is used.
+   * </ul>
+   */
   @Override
   public StartupOrder toStartupOrderForWriting(AppView<?> appView) {
-    LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses =
-        new LinkedHashSet<>(startupClasses.size());
+    LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
+        new LinkedHashSet<>(startupItems.size());
+    Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
+        computeSyntheticContextsToSyntheticClasses(appView);
+    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
+      addStartupItem(
+          startupItem, rewrittenStartupItems, syntheticContextsToSyntheticClasses, appView);
+    }
+    assert rewrittenStartupItems.stream().noneMatch(StartupItem::isSynthetic);
+    return createNonEmpty(rewrittenStartupItems);
+  }
+
+  private Map<DexType, List<DexProgramClass>> computeSyntheticContextsToSyntheticClasses(
+      AppView<?> appView) {
     Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
         new IdentityHashMap<>();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -106,84 +154,102 @@
         }
       }
     }
-    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
-      addStartupClass(
-          startupClass, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
-    }
-    assert rewrittenStartupClasses.stream().noneMatch(StartupClass::isSynthetic);
-    return createNonEmpty(rewrittenStartupClasses);
+    return syntheticContextsToSyntheticClasses;
   }
 
-  private static void addStartupClass(
-      StartupClass<DexType, DexMethod> startupClass,
-      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
+  private static void addStartupItem(
+      StartupItem<DexType, DexMethod, ?> startupItem,
+      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
       Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
       AppView<?> appView) {
-    if (startupClass.isSynthetic()) {
+    if (startupItem.isSynthetic()) {
+      assert startupItem.isStartupClass();
+      StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
       List<DexProgramClass> syntheticClassesForContext =
           syntheticContextsToSyntheticClasses.getOrDefault(
               startupClass.getReference(), Collections.emptyList());
       for (DexProgramClass clazz : syntheticClassesForContext) {
-        addClassAndParentClasses(clazz, rewrittenStartupClasses, appView);
+        addClassAndParentClasses(clazz, rewrittenStartupItems, appView);
+        addAllMethods(clazz, rewrittenStartupItems);
       }
     } else {
-      addClassAndParentClasses(startupClass.getReference(), rewrittenStartupClasses, appView);
+      if (startupItem.isStartupClass()) {
+        addClassAndParentClasses(
+            startupItem.asStartupClass().getReference(), rewrittenStartupItems, appView);
+      } else {
+        rewrittenStartupItems.add(startupItem);
+      }
     }
   }
 
   private static boolean addClass(
       DexProgramClass clazz,
-      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses) {
-    return rewrittenStartupClasses.add(
+      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems) {
+    return rewrittenStartupItems.add(
         StartupClass.dexBuilder().setClassReference(clazz.getType()).build());
   }
 
   private static void addClassAndParentClasses(
       DexType type,
-      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
+      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
       AppView<?> appView) {
     DexProgramClass definition = appView.app().programDefinitionFor(type);
     if (definition != null) {
-      addClassAndParentClasses(definition, rewrittenStartupClasses, appView);
+      addClassAndParentClasses(definition, rewrittenStartupItems, appView);
     }
   }
 
   private static void addClassAndParentClasses(
       DexProgramClass clazz,
-      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
+      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
       AppView<?> appView) {
-    if (addClass(clazz, rewrittenStartupClasses)) {
-      addParentClasses(clazz, rewrittenStartupClasses, appView);
+    if (addClass(clazz, rewrittenStartupItems)) {
+      addParentClasses(clazz, rewrittenStartupItems, appView);
     }
   }
 
   private static void addParentClasses(
       DexProgramClass clazz,
-      LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses,
+      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
       AppView<?> appView) {
     clazz.forEachImmediateSupertype(
-        supertype -> addClassAndParentClasses(supertype, rewrittenStartupClasses, appView));
+        supertype -> addClassAndParentClasses(supertype, rewrittenStartupItems, appView));
+  }
+
+  private static void addAllMethods(
+      DexProgramClass clazz,
+      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems) {
+    clazz.forEachProgramMethod(
+        method ->
+            rewrittenStartupItems.add(
+                StartupMethod.dexBuilder().setMethodReference(method.getReference()).build()));
   }
 
   @Override
   public StartupOrder withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) {
-    LinkedHashSet<StartupClass<DexType, DexMethod>> rewrittenStartupClasses =
-        new LinkedHashSet<>(startupClasses.size());
+    LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
+        new LinkedHashSet<>(startupItems.size());
     LazyBox<Set<DexType>> contextsOfLiveSynthetics =
         new LazyBox<>(
             () -> computeContextsOfLiveSynthetics(prunedItems.getPrunedApp(), syntheticItems));
-    for (StartupClass<DexType, DexMethod> startupClass : startupClasses) {
+    for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
       // Only prune non-synthetic classes, since the pruning of a class does not imply that all
       // classes synthesized from it have been pruned.
-      if (startupClass.isSynthetic()) {
+      if (startupItem.isSynthetic()) {
+        assert startupItem.isStartupClass();
+        StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
         if (contextsOfLiveSynthetics.computeIfAbsent().contains(startupClass.getReference())) {
-          rewrittenStartupClasses.add(startupClass);
+          rewrittenStartupItems.add(startupClass);
         }
-      } else if (!prunedItems.isRemoved(startupClass.getReference())) {
-        rewrittenStartupClasses.add(startupClass);
+      } else {
+        DexReference reference =
+            startupItem.apply(StartupClass::getReference, StartupMethod::getReference);
+        if (!prunedItems.isRemoved(reference)) {
+          rewrittenStartupItems.add(startupItem);
+        }
       }
     }
-    return createNonEmpty(rewrittenStartupClasses);
+    return createNonEmpty(rewrittenStartupItems);
   }
 
   private Set<DexType> computeContextsOfLiveSynthetics(
@@ -199,11 +265,11 @@
   }
 
   private StartupOrder createNonEmpty(
-      LinkedHashSet<StartupClass<DexType, DexMethod>> startupClasses) {
-    if (startupClasses.isEmpty()) {
+      LinkedHashSet<StartupItem<DexType, DexMethod, ?>> startupItems) {
+    if (startupItems.isEmpty()) {
       assert false;
       return empty();
     }
-    return new NonEmptyStartupOrder(startupClasses);
+    return new NonEmptyStartupOrder(startupItems);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
index 0f76036..7ae4d30 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
@@ -7,6 +7,10 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 // TODO(b/238173796): When updating the compiler to have support for taking a list of startup
 //  methods, this class may likely be removed along with the StartupItem class, so that only
@@ -25,6 +29,23 @@
     return new Builder<>();
   }
 
+  public static Builder<ClassReference, MethodReference> referenceBuilder() {
+    return new Builder<>();
+  }
+
+  @Override
+  public void accept(
+      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer) {
+    classConsumer.accept(this);
+  }
+
+  @Override
+  public <T> T apply(
+      Function<StartupClass<C, M>, T> classFunction,
+      Function<StartupMethod<C, M>, T> methodFunction) {
+    return classFunction.apply(this);
+  }
+
   @Override
   public boolean isStartupClass() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
index 9731423..2f1a23d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
@@ -21,10 +21,10 @@
 
 public class StartupConfiguration {
 
-  private final List<StartupClass<DexType, DexMethod>> startupClasses;
+  private final List<StartupItem<DexType, DexMethod, ?>> startupItems;
 
-  public StartupConfiguration(List<StartupClass<DexType, DexMethod>> startupClasses) {
-    this.startupClasses = startupClasses;
+  public StartupConfiguration(List<StartupItem<DexType, DexMethod, ?>> startupItems) {
+    this.startupItems = startupItems;
   }
 
   public static Builder builder() {
@@ -74,59 +74,43 @@
 
   public static StartupConfiguration createStartupConfigurationFromLines(
       DexItemFactory dexItemFactory, Reporter reporter, List<String> startupDescriptors) {
-    List<StartupClass<DexType, DexMethod>> startupClasses = new ArrayList<>();
+    List<StartupItem<DexType, DexMethod, ?>> startupItems = new ArrayList<>();
     StartupConfigurationParser.createDexParser(dexItemFactory)
         .parseLines(
             startupDescriptors,
-            startupClasses::add,
-            // TODO(b/238173796): Startup methods should be added as startup methods.
-            startupMethod ->
-                startupClasses.add(
-                    StartupClass.dexBuilder()
-                        .setClassReference(startupMethod.getReference().getHolderType())
-                        .setFlags(startupMethod.getFlags())
-                        .build()),
+            startupItems::add,
+            startupItems::add,
             error ->
                 reporter.warning(
                     new StringDiagnostic(
                         "Invalid descriptor for startup class or method: " + error)));
-    return new StartupConfiguration(startupClasses);
+    return new StartupConfiguration(startupItems);
   }
 
-  public boolean hasStartupClasses() {
-    return !startupClasses.isEmpty();
+  public boolean hasStartupItems() {
+    return !startupItems.isEmpty();
   }
 
-  public List<StartupClass<DexType, DexMethod>> getStartupClasses() {
-    return startupClasses;
+  public List<StartupItem<DexType, DexMethod, ?>> getStartupItems() {
+    return startupItems;
   }
 
   public static class Builder {
 
-    private final ImmutableList.Builder<StartupClass<DexType, DexMethod>> startupClassesBuilder =
+    private final ImmutableList.Builder<StartupItem<DexType, DexMethod, ?>> startupItemsBuilder =
         ImmutableList.builder();
 
     public Builder addStartupItem(StartupItem<DexType, DexMethod, ?> startupItem) {
-      if (startupItem.isStartupClass()) {
-        return addStartupClass(startupItem.asStartupClass());
-      } else {
-        assert startupItem.isStartupMethod();
-        return addStartupMethod(startupItem.asStartupMethod());
-      }
-    }
-
-    public Builder addStartupClass(StartupClass<DexType, DexMethod> startupClass) {
-      this.startupClassesBuilder.add(startupClass);
+      this.startupItemsBuilder.add(startupItem);
       return this;
     }
 
+    public Builder addStartupClass(StartupClass<DexType, DexMethod> startupClass) {
+      return addStartupItem(startupClass);
+    }
+
     public Builder addStartupMethod(StartupMethod<DexType, DexMethod> startupMethod) {
-      // TODO(b/238173796): Startup methods should be added as startup methods.
-      return addStartupClass(
-          StartupClass.dexBuilder()
-              .setFlags(startupMethod.getFlags())
-              .setClassReference(startupMethod.getReference().getHolderType())
-              .build());
+      return addStartupItem(startupMethod);
     }
 
     public Builder apply(Consumer<Builder> consumer) {
@@ -135,7 +119,7 @@
     }
 
     public StartupConfiguration build() {
-      return new StartupConfiguration(startupClassesBuilder.build());
+      return new StartupConfiguration(startupItemsBuilder.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
index 0678602..76abc9d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.experimental.startup;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.utils.PredicateUtils.not;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -18,6 +19,7 @@
 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.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
 import com.android.tools.r8.graph.DexValue.DexValueString;
@@ -32,9 +34,12 @@
 import com.android.tools.r8.ir.code.Value;
 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.startup.generated.InstrumentationServerFactory;
 import com.android.tools.r8.startup.generated.InstrumentationServerImplFactory;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.DescriptorUtils;
+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.ImmutableList;
@@ -48,15 +53,17 @@
   private final AppView<AppInfo> appView;
   private final IRConverter converter;
   private final DexItemFactory dexItemFactory;
-  private final StartupOptions options;
+  private final InternalOptions options;
   private final StartupReferences references;
+  private final StartupOptions startupOptions;
 
   private StartupInstrumentation(AppView<AppInfo> appView) {
     this.appView = appView;
     this.converter = new IRConverter(appView, Timing.empty());
     this.dexItemFactory = appView.dexItemFactory();
-    this.options = appView.options().getStartupOptions();
+    this.options = appView.options();
     this.references = new StartupReferences(dexItemFactory);
+    this.startupOptions = options.getStartupOptions();
   }
 
   public static void run(AppView<AppInfo> appView, ExecutorService executorService)
@@ -74,6 +81,24 @@
 
   private void injectStartupRuntimeLibrary(ExecutorService executorService)
       throws ExecutionException {
+    // Only inject the startup instrumentation server if it is not already in the app.
+    if (appView.definitionFor(references.instrumentationServerImplType) != null) {
+      return;
+    }
+
+    // If the startup options has a synthetic context for the startup instrumentation server, then
+    // only inject the runtime library if the synthetic context exists in program to avoid injecting
+    // the runtime library multiple times when there is separate compilation.
+    if (startupOptions.hasStartupInstrumentationServerSyntheticContext()) {
+      DexType syntheticContext =
+          dexItemFactory.createType(
+              DescriptorUtils.javaTypeToDescriptor(
+                  startupOptions.getStartupInstrumentationServerSyntheticContext()));
+      if (asProgramClassOrNull(appView.definitionFor(syntheticContext)) == null) {
+        return;
+      }
+    }
+
     List<DexProgramClass> extraProgramClasses = createStartupRuntimeLibraryClasses();
     converter.processClassesConcurrently(extraProgramClasses, executorService);
 
@@ -88,7 +113,7 @@
   private List<DexProgramClass> createStartupRuntimeLibraryClasses() {
     DexProgramClass instrumentationServerImplClass =
         InstrumentationServerImplFactory.createClass(dexItemFactory);
-    if (options.hasStartupInstrumentationTag()) {
+    if (startupOptions.hasStartupInstrumentationTag()) {
       instrumentationServerImplClass
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("writeToLogcat"))
           .setStaticValue(DexValueBoolean.create(true));
@@ -96,7 +121,7 @@
           .lookupUniqueStaticFieldWithName(dexItemFactory.createString("logcatTag"))
           .setStaticValue(
               new DexValueString(
-                  dexItemFactory.createString(options.getStartupInstrumentationTag())));
+                  dexItemFactory.createString(startupOptions.getStartupInstrumentationTag())));
     }
 
     return ImmutableList.of(
@@ -104,57 +129,93 @@
   }
 
   private void instrumentClass(DexProgramClass clazz) {
-    ensureClassInitializer(clazz);
-    clazz.forEachProgramMethod(this::instrumentMethod);
-  }
-
-  private void ensureClassInitializer(DexProgramClass clazz) {
-    if (!clazz.hasClassInitializer()) {
-      ComputedApiLevel computedApiLevel =
-          appView.apiLevelCompute().computeInitialMinApiLevel(appView.options());
-      DexReturnVoid returnInstruction = new DexReturnVoid();
-      returnInstruction.setOffset(0);
-      clazz.addDirectMethod(
-          DexEncodedMethod.syntheticBuilder()
-              .setAccessFlags(MethodAccessFlags.createForClassInitializer())
-              .setApiLevelForCode(computedApiLevel)
-              .setApiLevelForDefinition(computedApiLevel)
-              .setClassFileVersion(CfVersion.V1_6)
-              .setCode(new DexCode(0, 0, 0, new DexInstruction[] {returnInstruction}))
-              .setMethod(dexItemFactory.createClassInitializer(clazz.getType()))
-              .build());
-    }
-  }
-
-  private void instrumentMethod(ProgramMethod method) {
-    DexMethod methodToInvoke;
-    DexMethod methodToPrint;
-    SyntheticItems syntheticItems = appView.getSyntheticItems();
-    if (syntheticItems.isSyntheticClass(method.getHolder())) {
-      Collection<DexType> synthesizingContexts =
-          syntheticItems.getSynthesizingContextTypes(method.getHolderType());
-      assert synthesizingContexts.size() == 1;
-      DexType synthesizingContext = synthesizingContexts.iterator().next();
-      methodToInvoke = references.addSyntheticMethod;
-      methodToPrint = method.getReference().withHolder(synthesizingContext, dexItemFactory);
-    } else {
-      methodToInvoke = references.addNonSyntheticMethod;
-      methodToPrint = method.getReference();
+    // Do not instrument the instrumentation server if it is already in the app.
+    if (clazz.getType() == references.instrumentationServerType
+        || clazz.getType() == references.instrumentationServerImplType) {
+      return;
     }
 
-    IRCode code = method.buildIR(appView);
+    boolean addedClassInitializer = ensureClassInitializer(clazz);
+    clazz.forEachProgramMethodMatching(
+        DexEncodedMethod::hasCode,
+        method ->
+            instrumentMethod(
+                method, method.getDefinition().isClassInitializer() && addedClassInitializer));
+  }
+
+  private boolean ensureClassInitializer(DexProgramClass clazz) {
+    if (clazz.hasClassInitializer()) {
+      return false;
+    }
+    ComputedApiLevel computedApiLevel =
+        appView.apiLevelCompute().computeInitialMinApiLevel(options);
+    DexReturnVoid returnInstruction = new DexReturnVoid();
+    returnInstruction.setOffset(0);
+    clazz.addDirectMethod(
+        DexEncodedMethod.syntheticBuilder()
+            .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+            .setApiLevelForCode(computedApiLevel)
+            .setApiLevelForDefinition(computedApiLevel)
+            .setClassFileVersion(CfVersion.V1_6)
+            .setCode(new DexCode(0, 0, 0, new DexInstruction[] {returnInstruction}))
+            .setMethod(dexItemFactory.createClassInitializer(clazz.getType()))
+            .build());
+    return true;
+  }
+
+  private void instrumentMethod(ProgramMethod method, boolean skipMethodLogging) {
+    // Disable StringSwitch conversion to avoid having to run the StringSwitchRemover before
+    // finalizing the code.
+    MutableMethodConversionOptions conversionOptions =
+        new MutableMethodConversionOptions(options).disableStringSwitchConversion();
+    IRCode code = method.buildIR(appView, conversionOptions);
     InstructionListIterator instructionIterator = code.entryBlock().listIterator(code);
     instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
 
-    Value descriptorValue =
-        instructionIterator.insertConstStringInstruction(
-            appView, code, dexItemFactory.createString(methodToPrint.toSmaliString()));
-    instructionIterator.add(
-        InvokeStatic.builder()
-            .setMethod(methodToInvoke)
-            .setSingleArgument(descriptorValue)
-            .setPosition(Position.syntheticNone())
-            .build());
+    // Insert invoke to record that the enclosing class is a startup class.
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    boolean isSyntheticClass = syntheticItems.isSyntheticClass(method.getHolder());
+    if (method.getDefinition().isClassInitializer() && !isSyntheticClass) {
+      DexMethod methodToInvoke = references.addNonSyntheticMethod;
+      DexType classToPrint = method.getHolderType();
+      Value descriptorValue =
+          instructionIterator.insertConstStringInstruction(
+              appView, code, dexItemFactory.createString(classToPrint.toSmaliString()));
+      instructionIterator.add(
+          InvokeStatic.builder()
+              .setMethod(methodToInvoke)
+              .setSingleArgument(descriptorValue)
+              .setPosition(Position.syntheticNone())
+              .build());
+    }
+
+    // Insert invoke to record the execution of the current method.
+    if (!skipMethodLogging) {
+      DexMethod methodToInvoke;
+      DexReference referenceToPrint;
+      if (isSyntheticClass) {
+        Collection<DexType> synthesizingContexts =
+            syntheticItems.getSynthesizingContextTypes(method.getHolderType());
+        assert synthesizingContexts.size() == 1;
+        DexType synthesizingContext = synthesizingContexts.iterator().next();
+        methodToInvoke = references.addSyntheticMethod;
+        referenceToPrint = synthesizingContext;
+      } else {
+        methodToInvoke = references.addNonSyntheticMethod;
+        referenceToPrint = method.getReference();
+      }
+
+      Value descriptorValue =
+          instructionIterator.insertConstStringInstruction(
+              appView, code, dexItemFactory.createString(referenceToPrint.toSmaliString()));
+      instructionIterator.add(
+          InvokeStatic.builder()
+              .setMethod(methodToInvoke)
+              .setSingleArgument(descriptorValue)
+              .setPosition(Position.syntheticNone())
+              .build());
+    }
+
     DexCode instrumentedCode =
         new IRToDexFinalizer(appView, converter.deadCodeRemover)
             .finalizeCode(code, BytecodeMetadataProvider.empty(), Timing.empty());
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
index c745c90..74e69e2 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupItem.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 public abstract class StartupItem<C, M, R> {
 
@@ -20,6 +21,13 @@
     this.reference = reference;
   }
 
+  public abstract void accept(
+      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer);
+
+  public abstract <T> T apply(
+      Function<StartupClass<C, M>, T> classFunction,
+      Function<StartupMethod<C, M>, T> methodFunction);
+
   public boolean isStartupClass() {
     return false;
   }
@@ -118,6 +126,14 @@
       return self();
     }
 
+    public B setSynthetic(boolean synthetic) {
+      if (synthetic) {
+        return setSynthetic();
+      }
+      assert (flags & FLAG_SYNTHETIC) == 0;
+      return self();
+    }
+
     public StartupItem<C, M, ?> build() {
       if (classReference != null) {
         return buildStartupClass();
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java
index 3109611..871e179 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupMethod.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 public class StartupMethod<C, M> extends StartupItem<C, M, M> {
 
@@ -19,6 +21,19 @@
   }
 
   @Override
+  public void accept(
+      Consumer<StartupClass<C, M>> classConsumer, Consumer<StartupMethod<C, M>> methodConsumer) {
+    methodConsumer.accept(this);
+  }
+
+  @Override
+  public <T> T apply(
+      Function<StartupClass<C, M>, T> classFunction,
+      Function<StartupMethod<C, M>, T> methodFunction) {
+    return methodFunction.apply(this);
+  }
+
+  @Override
   public boolean isStartupMethod() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
index 15fbf6c..7ff913d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -9,19 +9,76 @@
 
 public class StartupOptions {
 
+  /**
+   * When enabled, all startup classes will be placed in the primary classes.dex file. All other
+   * (non-startup) classes will be placed in classes2.dex, ..., classesN.dex.
+   */
   private boolean enableMinimalStartupDex =
       parseSystemPropertyForDevelopmentOrDefault(
           "com.android.tools.r8.startup.minimalstartupdex", false);
+
+  /**
+   * When enabled, each method that is not classified as a startup method at the end of compilation
+   * will be changed to have a throwing method body.
+   *
+   * <p>This is useful for testing if a given startup list is complete (and that R8 correctly
+   * rewrites the startup list in presence of optimizations).
+   */
   private boolean enableStartupCompletenessCheckForTesting =
       parseSystemPropertyForDevelopmentOrDefault(
           "com.android.tools.r8.startup.completenesscheck", false);
+
+  /**
+   * When enabled, each method will be instrumented to notify the startup InstrumentationServer that
+   * it has been executed.
+   *
+   * <p>This will also inject the startup runtime library (i.e., the InstrumentationServer) into the
+   * app.
+   */
   private boolean enableStartupInstrumentation =
       parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.instrument", false);
+
+  /**
+   * Specifies the synthetic context of the startup runtime library. When this is set, the startup
+   * runtime library will only be injected into the app when the synthetic context is in the
+   * program. This can be used to avoid that the startup runtime library is injected multiple times
+   * in presence of separate compilation.
+   *
+   * <p>Example synthetic context: "app.tivi.home.MainActivity".
+   *
+   * <p>Note that this is only meaningful when {@link #enableStartupInstrumentation} is set to true.
+   */
+  private String startupInstrumentationServerSyntheticContext =
+      getSystemPropertyForDevelopment(
+          "com.android.tools.r8.startup.instrumentationserversyntheticcontext");
+
+  /**
+   * Specifies the logcat tag that should be used by the InstrumentationServer when logging events.
+   *
+   * <p>When a logcat tag is not specified, the InstrumentationServer will not print events to
+   * logcat. Instead, the startup events must be obtained by requesting the InstrumentationServer to
+   * write the events to a file.
+   */
   private String startupInstrumentationTag =
       getSystemPropertyForDevelopment("com.android.tools.r8.startup.instrumentationtag");
 
   private StartupConfiguration startupConfiguration;
 
+  public boolean hasStartupInstrumentationServerSyntheticContext() {
+    return startupInstrumentationServerSyntheticContext != null;
+  }
+
+  public String getStartupInstrumentationServerSyntheticContext() {
+    return startupInstrumentationServerSyntheticContext;
+  }
+
+  public StartupOptions setStartupInstrumentationServerSyntheticContext(
+      String startupInstrumentationServerSyntheticContext) {
+    this.startupInstrumentationServerSyntheticContext =
+        startupInstrumentationServerSyntheticContext;
+    return this;
+  }
+
   public boolean hasStartupInstrumentationTag() {
     return startupInstrumentationTag != null;
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
index 1676991..b1803eb 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
@@ -24,10 +24,10 @@
     }
     StartupConfiguration startupConfiguration =
         options.getStartupOptions().getStartupConfiguration();
-    if (!startupConfiguration.hasStartupClasses()) {
+    if (!startupConfiguration.hasStartupItems()) {
       return empty();
     }
-    return new NonEmptyStartupOrder(new LinkedHashSet<>(startupConfiguration.getStartupClasses()));
+    return new NonEmptyStartupOrder(new LinkedHashSet<>(startupConfiguration.getStartupItems()));
   }
 
   public static StartupOrder empty() {
@@ -36,7 +36,7 @@
 
   public abstract boolean contains(DexType type, SyntheticItems syntheticItems);
 
-  public abstract Collection<StartupClass<DexType, DexMethod>> getClasses();
+  public abstract Collection<StartupItem<DexType, DexMethod, ?>> getItems();
 
   public abstract boolean isEmpty();
 
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java
index 0c2de77..a041f1e 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupReferences.java
@@ -10,11 +10,14 @@
 
 public class StartupReferences {
 
+  final DexType instrumentationServerType;
   final DexType instrumentationServerImplType;
   final DexMethod addNonSyntheticMethod;
   final DexMethod addSyntheticMethod;
 
   StartupReferences(DexItemFactory dexItemFactory) {
+    instrumentationServerType =
+        dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServer;");
     instrumentationServerImplType =
         dexItemFactory.createType("Lcom/android/tools/r8/startup/InstrumentationServerImpl;");
     addNonSyntheticMethod =
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 1a029f8..b891c89 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.Deque;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -556,6 +557,16 @@
         .lookupMaximallySpecificTarget(clazz, method);
   }
 
+  /**
+   * Helper methods used for emulated interface resolution (not in JVM specifications). Answers the
+   * abstract interface methods that the resolution could but does not necessarily resolve into.
+   */
+  public List<Entry<DexClass, DexEncodedMethod>> getAbstractInterfaceMethods(
+      DexClass clazz, DexMethod method) {
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
+        .getAbstractInterfaceMethods(clazz, method);
+  }
+
   MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
     return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .resolveMaximallySpecificTarget(clazz, method);
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 5dd4a97..49bfc86 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -7,12 +7,10 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.DataResourceProvider;
-import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.synthesis.SyntheticDefinitionsProvider;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -265,13 +263,7 @@
   }
 
   public static LazyLoadedDexApplication.Builder builder(InternalOptions options, Timing timing) {
-    return builder(
-        options, timing, ProgramClassCollection.defaultConflictResolver(options.reporter));
-  }
-
-  public static LazyLoadedDexApplication.Builder builder(
-      InternalOptions options, Timing timing, ProgramClassConflictResolver resolver) {
-    return new LazyLoadedDexApplication.Builder(resolver, options, timing);
+    return new LazyLoadedDexApplication.Builder(options, timing);
   }
 
   public DirectMappedDexApplication asDirect() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 8859c3c..610a0c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -326,10 +326,6 @@
         .withNullableItem(m -> m.rewrittenTarget);
   }
 
-  public Handle toAsmHandle() {
-    return toAsmHandle(NamingLens.getIdentityLens());
-  }
-
   public Handle toAsmHandle(NamingLens lens) {
     String owner;
     String name;
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index afba589..880b51d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -104,6 +104,12 @@
     }
   }
 
+  public void forEachReverse(Consumer<? super DexType> consumer) {
+    for (int i = values.length - 1; i >= 0; i--) {
+      consumer.accept(values[i]);
+    }
+  }
+
   @Override
   public int hashCode() {
     return Arrays.hashCode(values);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 73606cc..f648f90 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -534,6 +534,13 @@
         return;
       }
       application.addRecordWitness(type, classKind);
+      if (classKind != ClassKind.PROGRAM) {
+        // Non program classes may just be headers, in which case instance fields and annotations
+        // may be partially stripped, leading to non matching record components and instance fields.
+        // Record desugaring does not need information beyond the record flag on non program class,
+        // so it's safe to compile even if there is a missmatch.
+        return;
+      }
       // TODO(b/169645628): Change this logic if we start stripping the record components.
       // Another approach would be to mark a bit in fields that are record components instead.
       String message = "Records are expected to have one record component per instance field.";
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 584729a..80bb4cf 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -271,18 +271,15 @@
 
     private ClasspathClassCollection classpathClasses;
     private LibraryClassCollection libraryClasses;
-    private final ProgramClassConflictResolver resolver;
 
-    Builder(ProgramClassConflictResolver resolver, InternalOptions options, Timing timing) {
+    Builder(InternalOptions options, Timing timing) {
       super(options, timing);
-      this.resolver = resolver;
       this.classpathClasses = ClasspathClassCollection.empty();
       this.libraryClasses = null;
     }
 
     private Builder(LazyLoadedDexApplication application) {
       super(application);
-      this.resolver = ProgramClassCollection.defaultConflictResolver(application.options.reporter);
       this.classpathClasses = application.classpathClasses;
       this.libraryClasses = application.libraryClasses;
     }
@@ -311,6 +308,10 @@
 
     @Override
     public LazyLoadedDexApplication build() {
+      ProgramClassConflictResolver resolver =
+          options.programClassConflictResolver == null
+              ? ProgramClassCollection.defaultConflictResolver(options.reporter)
+              : options.programClassConflictResolver;
       return new LazyLoadedDexApplication(
           proguardMap,
           flags,
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
index f7271b7..11f9bd9 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -241,6 +241,12 @@
     return resolveMaximallySpecificTargetHelper(clazz, method).lookup();
   }
 
+  // Non-private method used for emulated interface only.
+  List<Entry<DexClass, DexEncodedMethod>> getAbstractInterfaceMethods(
+      DexClass clazz, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(clazz, method).getAbstractMethods();
+  }
+
   private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
       DexClass clazz, DexMethod method) {
     MaximallySpecificMethodsBuilder builder =
@@ -706,6 +712,28 @@
       return nonAbstractMethods;
     }
 
+    List<Entry<DexClass, DexEncodedMethod>> getAbstractMethods() {
+      List<Entry<DexClass, DexEncodedMethod>> abstractMethods = new ArrayList<>();
+      addAbstractMethods(abstractMethods, maximallySpecificMethodsOnCompletePaths);
+      addAbstractMethods(abstractMethods, maximallySpecificMethodsOnIncompletePaths);
+      return abstractMethods;
+    }
+
+    private void addAbstractMethods(
+        List<Entry<DexClass, DexEncodedMethod>> abstractMethods,
+        Map<DexClass, DexEncodedMethod> candidates) {
+      for (Entry<DexClass, DexEncodedMethod> entry : candidates.entrySet()) {
+        DexEncodedMethod method = entry.getValue();
+        if (method == null) {
+          // Ignore shadowed candidates.
+          continue;
+        }
+        if (method.isAbstract()) {
+          abstractMethods.add(entry);
+        }
+      }
+    }
+
     private static SingleResolutionResult<?> singleResultHelper(
         DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) {
       return MethodResolutionResult.createSingleResolutionResult(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 2154c8c..173161f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -10,6 +10,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_METHOD_HANDLE;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_METHOD_TYPE;
 import static com.android.tools.r8.ir.code.Opcodes.INIT_CLASS;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
@@ -46,6 +47,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.FieldLookupResult;
@@ -70,6 +72,7 @@
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstMethodHandle;
+import com.android.tools.r8.ir.code.ConstMethodType;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.FieldPut;
 import com.android.tools.r8.ir.code.IRCode;
@@ -85,6 +88,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMultiNewArray;
 import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.MoveException;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -99,6 +103,7 @@
 import com.android.tools.r8.ir.code.UnusedArgument;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.optimize.argumentpropagation.lenscoderewriter.NullCheckInserter;
@@ -220,6 +225,15 @@
       assert graphLens.getPrevious() == codeLens;
       affectedPhis.addAll(enumUnboxer.rewriteCode(code, methodProcessor, prototypeChanges));
     }
+    if (!unusedArguments.isEmpty()) {
+      for (UnusedArgument unusedArgument : unusedArguments) {
+        if (unusedArgument.outValue().hasPhiUsers()) {
+          // See b/240282988: We can end up in situations where the second round of IR processing
+          // introduce phis for irreducible control flow, we need to resolve them.
+          CodeRewriter.replaceUnusedArgumentTrivialPhis(unusedArgument);
+        }
+      }
+    }
     rewritePartialDefault(
         code, method, graphLens, codeLens, prototypeChanges, affectedPhis, unusedArguments);
   }
@@ -277,11 +291,18 @@
                       .computeIfAbsent()
                       .rewriteDexMethodHandle(handle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, method);
               if (newHandle != handle) {
-                Value newOutValue = makeOutValue(current, code, graphLens, codeLens);
-                iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
-                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
-                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
-                }
+                iterator.replaceCurrentInstruction(
+                    new ConstMethodHandle(current.outValue(), newHandle));
+              }
+            }
+            break;
+          case CONST_METHOD_TYPE:
+            {
+              ConstMethodType constType = current.asConstMethodType();
+              DexProto rewrittenProto = helper.computeIfAbsent().rewriteProto(constType.getValue());
+              if (constType.getValue() != rewrittenProto) {
+                iterator.replaceCurrentInstruction(
+                    new ConstMethodType(constType.outValue(), rewrittenProto));
               }
             }
             break;
@@ -298,9 +319,25 @@
             }
             break;
 
+          case INVOKE_POLYMORPHIC:
+            {
+              InvokePolymorphic invoke = current.asInvokePolymorphic();
+              // The invoked method is on java.lang.invoke.MethodHandle and always remains as is.
+              assert factory.polymorphicMethods.isPolymorphicInvoke(invoke.getInvokedMethod());
+              // Rewrite the signature of the handles actual target.
+              DexProto rewrittenProto = helper.computeIfAbsent().rewriteProto(invoke.getProto());
+              if (invoke.getProto() != rewrittenProto) {
+                iterator.replaceCurrentInstruction(
+                    new InvokePolymorphic(
+                        invoke.getInvokedMethod(),
+                        rewrittenProto,
+                        invoke.outValue(),
+                        invoke.arguments()));
+              }
+            }
+            break;
           case INVOKE_DIRECT:
           case INVOKE_INTERFACE:
-          case INVOKE_POLYMORPHIC:
           case INVOKE_STATIC:
           case INVOKE_SUPER:
           case INVOKE_VIRTUAL:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
index 038a277..adf0102 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
@@ -35,8 +35,9 @@
       enablePeepholeOptimizations = false;
     }
 
-    public void disableStringSwitchConversion() {
+    public MutableMethodConversionOptions disableStringSwitchConversion() {
       enableStringSwitchConversion = false;
+      return this;
     }
 
     public MutableMethodConversionOptions setIsGeneratingClassFiles(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index c48cd2f..3e41a70 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -137,9 +137,7 @@
     if (recordRewriter != null) {
       desugarings.add(recordRewriter);
     }
-    if (appView.options().enableUnrepresentableInDexInstructionRemoval) {
-      yieldingDesugarings.add(new UnrepresentableInDexInstructionRemover(appView));
-    }
+    yieldingDesugarings.add(new UnrepresentableInDexInstructionRemover(appView));
   }
 
   static NonEmptyCfInstructionDesugaringCollection createForCfToCfNonDesugar(AppView<?> appView) {
@@ -160,10 +158,8 @@
         new NonEmptyCfInstructionDesugaringCollection(appView, noAndroidApiLevelCompute());
     desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     desugaringCollection.desugarings.add(new InvokeToPrivateRewriter());
-    if (appView.options().enableUnrepresentableInDexInstructionRemoval) {
-      desugaringCollection.yieldingDesugarings.add(
-          new UnrepresentableInDexInstructionRemover(appView));
-    }
+    desugaringCollection.yieldingDesugarings.add(
+        new UnrepresentableInDexInstructionRemover(appView));
     return desugaringCollection;
   }
 
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 6d6dc0f..24cd2b5 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
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
@@ -79,7 +80,7 @@
             makeDiagnostic(context.getOrigin(), MethodPosition.create(context));
         assert (diagnostic.getSupportedApiLevel() == -1 && supportedApiLevel == null)
             || (diagnostic.getSupportedApiLevel() == supportedApiLevel.getLevel());
-        appView.reporter().error(diagnostic);
+        appView.reporter().warning(diagnostic);
       }
     }
 
@@ -114,8 +115,9 @@
       builder.add(new CfStackInstruction(type.isWideType() ? Opcode.Pop2 : Opcode.Pop));
     }
 
-    static void pop(Iterable<DexType> types, Builder<CfInstruction> builder) {
-      types.forEach(t -> pop(t, builder));
+    static void pop(DexProto proto, Builder<CfInstruction> builder) {
+      // Pop arguments in reverse order from the stack.
+      proto.getParameters().forEachReverse(t -> pop(t, builder));
     }
 
     static Builder<CfInstruction> pushReturnValue(DexType type, Builder<CfInstruction> builder) {
@@ -169,7 +171,7 @@
                 report(context);
                 Builder<CfInstruction> replacement = ImmutableList.builder();
                 DexCallSite callSite = invokeDynamic.getCallSite();
-                pop(callSite.getMethodProto().getParameters(), replacement);
+                pop(callSite.getMethodProto(), replacement);
                 localStackAllocator.allocateLocalStack(1);
                 invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
                 pushReturnValue(callSite.getMethodProto().getReturnType(), replacement);
@@ -217,10 +219,10 @@
                   dexItemFactory) -> {
                 report(context);
                 Builder<CfInstruction> replacement = ImmutableList.builder();
+                pop(invoke.getMethod().getProto(), replacement);
                 if (!invoke.isInvokeStatic()) {
                   pop(dexItemFactory.objectType, replacement);
                 }
-                pop(invoke.getMethod().getParameters(), replacement);
                 localStackAllocator.allocateLocalStack(1);
                 invokeThrowingStub(methodProcessingContext, eventConsumer, context, replacement);
                 pushReturnValue(invoke.getMethod().getReturnType(), replacement);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
index 66a980a..e13fb7c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
@@ -50,7 +50,7 @@
     }
     ApiLevelRange that = (ApiLevelRange) o;
     return apiLevelBelowOrEqual.equals(that.apiLevelBelowOrEqual)
-        && apiLevelGreaterOrEqual.equals(that.apiLevelGreaterOrEqual);
+        && Objects.equals(apiLevelGreaterOrEqual, that.apiLevelGreaterOrEqual);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
index ba352b1..233374b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecification.java
@@ -7,11 +7,8 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collection;
 import java.util.List;
 
 public interface DesugaredLibrarySpecification {
@@ -38,17 +35,4 @@
 
   MachineDesugaredLibrarySpecification toMachineSpecification(DexApplication app, Timing timing)
       throws IOException;
-
-  MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options,
-      Collection<Path> library,
-      Timing timing,
-      Collection<Path> desugaredJDKLib)
-      throws IOException;
-
-  default MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options, Collection<Path> library, Timing timing) throws IOException {
-    assert !isLibraryCompilation();
-    return toMachineSpecification(options, library, timing, null);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecificationParser.java
index 83ee3ec..bc61d60 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibrarySpecificationParser.java
@@ -8,7 +8,9 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecificationParser;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -20,6 +22,7 @@
 
   public static final String CONFIGURATION_FORMAT_VERSION_KEY = "configuration_format_version";
   private static final int MIN_HUMAN_CONFIGURATION_FORMAT_VERSION = 100;
+  private static final int MIN_MACHINE_CONFIGURATION_FORMAT_VERSION = 200;
 
   public static DesugaredLibrarySpecification parseDesugaredLibrarySpecification(
       StringResource stringResource,
@@ -27,26 +30,8 @@
       Reporter reporter,
       boolean libraryCompilation,
       int minAPILevel) {
-    Origin origin = stringResource.getOrigin();
-    assert origin != null;
-    String jsonConfigString;
-    JsonObject jsonConfig;
-    try {
-      jsonConfigString = stringResource.getString();
-      JsonParser parser = new JsonParser();
-      jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
-    } catch (Exception e) {
-      throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
-    }
-
-    if (isHumanSpecification(jsonConfig, reporter, origin)) {
-      return new HumanDesugaredLibrarySpecificationParser(
-              dexItemFactory, reporter, libraryCompilation, minAPILevel)
-          .parse(origin, jsonConfigString, jsonConfig);
-    }
-    return new LegacyDesugaredLibrarySpecificationParser(
-            dexItemFactory, reporter, libraryCompilation, minAPILevel)
-        .parse(origin, jsonConfigString, jsonConfig);
+    return parseDesugaredLibrarySpecificationforTesting(
+        stringResource, dexItemFactory, reporter, libraryCompilation, minAPILevel, flags -> {});
   }
 
   public static DesugaredLibrarySpecification parseDesugaredLibrarySpecificationforTesting(
@@ -67,18 +52,53 @@
     } catch (Exception e) {
       throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
     }
+    // Machine Specification is the shippable format released in Maven. D8/R8 has to be *very*
+    // backward compatible to any machine specification, and raise proper error messages for
+    // compatibility issues. The format is also exhaustive (Very limited pattern matching, if any).
+    // It can hardly be written by hand and is always generated.
+    if (isMachineSpecification(jsonConfig, reporter, origin)) {
+      return new MachineDesugaredLibrarySpecificationParser(
+              dexItemFactory, reporter, libraryCompilation, minAPILevel, new SyntheticNaming())
+          .parse(origin, jsonConfigString, jsonConfig);
+    }
+    // Human Specification is the easy to write format for developers and allows one to widely use
+    // pattern matching. This format is mainly used for development and to generate the machine
+    // specification. D8/R8 is *not* backward compatible with any previous version of human
+    // specification, which is therefore not suited to be shipped for external users. It can be
+    // shipped to internal users where we can easily update the D8/R8 compiler and the
+    // desugared library specification at the same time.
     if (isHumanSpecification(jsonConfig, reporter, origin)) {
       return new HumanDesugaredLibrarySpecificationParser(
               dexItemFactory, reporter, libraryCompilation, minAPILevel)
           .parse(origin, jsonConfigString, jsonConfig, topLevelFlagsAmender);
     }
+    // Legacy specification is the legacy format, as was shipped desugared library JDK8.
+    // Hopefully the day will come where this format is no longer supported, and the other formats
+    // shall always be preferred+.
     return new LegacyDesugaredLibrarySpecificationParser(
             dexItemFactory, reporter, libraryCompilation, minAPILevel)
         .parse(origin, jsonConfigString, jsonConfig, topLevelFlagsAmender);
   }
 
+  public static boolean isMachineSpecification(
+      JsonObject jsonConfig, Reporter reporter, Origin origin) {
+    ensureConfigurationFormatVersion(jsonConfig, reporter, origin);
+
+    int formatVersion = jsonConfig.get(CONFIGURATION_FORMAT_VERSION_KEY).getAsInt();
+    return formatVersion >= MIN_MACHINE_CONFIGURATION_FORMAT_VERSION;
+  }
+
   public static boolean isHumanSpecification(
       JsonObject jsonConfig, Reporter reporter, Origin origin) {
+    ensureConfigurationFormatVersion(jsonConfig, reporter, origin);
+
+    int formatVersion = jsonConfig.get(CONFIGURATION_FORMAT_VERSION_KEY).getAsInt();
+    return formatVersion >= MIN_HUMAN_CONFIGURATION_FORMAT_VERSION
+        && formatVersion < MIN_MACHINE_CONFIGURATION_FORMAT_VERSION;
+  }
+
+  private static void ensureConfigurationFormatVersion(
+      JsonObject jsonConfig, Reporter reporter, Origin origin) {
     if (!jsonConfig.has(CONFIGURATION_FORMAT_VERSION_KEY)) {
       throw reporter.fatalError(
           new StringDiagnostic(
@@ -87,8 +107,5 @@
                   + "'",
               origin));
     }
-
-    return jsonConfig.get(CONFIGURATION_FORMAT_VERSION_KEY).getAsInt()
-        >= MIN_HUMAN_CONFIGURATION_FORMAT_VERSION;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
index e3696af..49cc5f6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecification.java
@@ -8,11 +8,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collection;
 import java.util.List;
 
 public class HumanDesugaredLibrarySpecification implements DesugaredLibrarySpecification {
@@ -95,15 +91,4 @@
       DexApplication app, Timing timing) {
     return new HumanToMachineSpecificationConverter(timing).convert(this, app);
   }
-
-  @Override
-  public MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options,
-      Collection<Path> library,
-      Timing timing,
-      Collection<Path> desugaredJDKLib)
-      throws IOException {
-    return new HumanToMachineSpecificationConverter(timing)
-        .convertForTesting(this, desugaredJDKLib, library, options);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 1aa5f62..5f8e35a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.TopLevelFlagsBuilder;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser.HumanFieldParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser.HumanMethodParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.HumanFieldParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.HumanMethodParser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
index e669bfb..cd236c0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/legacyspecification/LegacyDesugaredLibrarySpecification.java
@@ -14,11 +14,8 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -125,16 +122,4 @@
         .convert(this, app)
         .toMachineSpecification(app, timing);
   }
-
-  @Override
-  public MachineDesugaredLibrarySpecification toMachineSpecification(
-      InternalOptions options,
-      Collection<Path> library,
-      Timing timing,
-      Collection<Path> desugaredJDKLib)
-      throws IOException {
-    return new LegacyToHumanSpecificationConverter(timing)
-        .convertForTesting(this, desugaredJDKLib, library, options)
-        .toMachineSpecification(options, library, timing, desugaredJDKLib);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/GenerateLintFiles.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index af340d4..3de3ac0 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -1,9 +1,11 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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;
+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.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -47,10 +49,12 @@
 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;
@@ -64,6 +68,7 @@
 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;
@@ -104,9 +109,8 @@
     DesugaredLibrarySpecification specification =
         readDesugaredLibraryConfiguration(desugarConfigurationPath);
     Path androidJarPath = getAndroidJarPath(specification.getRequiredCompilationApiLevel());
-    this.desugaredLibrarySpecification =
-        specification.toMachineSpecification(
-            options, ImmutableList.of(androidJarPath), Timing.empty());
+    DexApplication app = createApp(androidJarPath, options);
+    this.desugaredLibrarySpecification = specification.toMachineSpecification(app, Timing.empty());
 
     this.desugaredLibraryImplementation = desugarImplementationPath;
     this.outputDirectory = outputDirectory;
@@ -250,8 +254,7 @@
     Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
     Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
     for (DexProgramClass clazz : dexApplication.classes()) {
-      if (clazz.accessFlags.isPublic()
-          && desugaredLibrarySpecification.getRewriteType().containsKey(clazz.type)) {
+      if (clazz.accessFlags.isPublic() && desugaredLibrarySpecification.isSupported(clazz.type)) {
         DexProgramClass implementationClass =
             implementationApplication.programDefinitionFor(clazz.getType());
         if (implementationClass == null) {
@@ -420,6 +423,19 @@
         });
   }
 
+  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();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java
index e8c3ce3..6579f8f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/CustomConversionDescriptor.java
@@ -5,8 +5,9 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
 import com.android.tools.r8.graph.DexMethod;
+import java.util.Objects;
 
-public class CustomConversionDescriptor {
+public class CustomConversionDescriptor implements SpecificationDescriptor {
   private final DexMethod to;
   private final DexMethod from;
 
@@ -24,4 +25,27 @@
   public DexMethod getFrom() {
     return from;
   }
+
+  @Override
+  public Object[] toJsonStruct(
+      MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
+    return exporter.exportCustomConversionDescriptor(this);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof CustomConversionDescriptor)) {
+      return false;
+    }
+    CustomConversionDescriptor that = (CustomConversionDescriptor) o;
+    return to == that.to && from == that.from;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(to, from);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
index c383dcd..dde1f48 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.Objects;
 
 /**
  * A derived method is: - if the holderKind is null, a normal dexMethod; - if the holderKind is
@@ -16,7 +17,7 @@
  * context to generate the holder type. The method may however differ (for example the method name
  * may be different).
  */
-public class DerivedMethod {
+public class DerivedMethod implements SpecificationDescriptor {
 
   private final DexMethod method;
   private final SyntheticKind holderKind;
@@ -49,4 +50,27 @@
   public DexProto getProto() {
     return method.getProto();
   }
+
+  @Override
+  public Object[] toJsonStruct(
+      MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
+    return exporter.exportDerivedMethod(this);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof DerivedMethod)) {
+      return false;
+    }
+    DerivedMethod that = (DerivedMethod) o;
+    return method == that.method && Objects.equals(holderKind, that.holderKind);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(method, holderKind);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
index 6eb2424..9737d09 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
@@ -6,8 +6,9 @@
 
 import com.android.tools.r8.graph.DexType;
 import java.util.LinkedHashMap;
+import java.util.Objects;
 
-public class EmulatedDispatchMethodDescriptor {
+public class EmulatedDispatchMethodDescriptor implements SpecificationDescriptor {
 
   /**
    * When resolving into the descriptor, if the resolution is used for a super-invoke or to generate
@@ -67,4 +68,30 @@
   public LinkedHashMap<DexType, DerivedMethod> getDispatchCases() {
     return dispatchCases;
   }
+
+  @Override
+  public Object[] toJsonStruct(
+      MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
+    return exporter.exportEmulatedDispatchMethodDescriptor(this);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof EmulatedDispatchMethodDescriptor)) {
+      return false;
+    }
+    EmulatedDispatchMethodDescriptor that = (EmulatedDispatchMethodDescriptor) o;
+    return Objects.equals(interfaceMethod, that.interfaceMethod)
+        && Objects.equals(emulatedDispatchMethod, that.emulatedDispatchMethod)
+        && Objects.equals(forwardingMethod, that.forwardingMethod)
+        && Objects.equals(dispatchCases, that.dispatchCases);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(interfaceMethod, emulatedDispatchMethod, forwardingMethod, dispatchCases);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
index 0f9a520..fb96fe6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -7,8 +7,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import java.util.Map;
+import java.util.Objects;
 
-public class EmulatedInterfaceDescriptor {
+public class EmulatedInterfaceDescriptor implements SpecificationDescriptor {
   private final DexType rewrittenType;
   private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedMethods;
 
@@ -25,4 +26,27 @@
   public Map<DexMethod, EmulatedDispatchMethodDescriptor> getEmulatedMethods() {
     return emulatedMethods;
   }
+
+  @Override
+  public Object[] toJsonStruct(
+      MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
+    return exporter.exportEmulatedInterfaceDescriptor(this);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof EmulatedInterfaceDescriptor)) {
+      return false;
+    }
+    EmulatedInterfaceDescriptor that = (EmulatedInterfaceDescriptor) o;
+    return rewrittenType == that.rewrittenType && emulatedMethods.equals(that.emulatedMethods);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(rewrittenType, emulatedMethods);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 4aa99b6..f4a6d2a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -4,20 +4,24 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SemanticVersion;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 
-public class MachineDesugaredLibrarySpecification {
+public class MachineDesugaredLibrarySpecification implements DesugaredLibrarySpecification {
 
   private final boolean libraryCompilation;
   private final MachineTopLevelFlags topLevelFlags;
@@ -52,18 +56,30 @@
     this.rewritingFlags = rewritingFlags;
   }
 
+  @Override
   public boolean isEmpty() {
     return rewritingFlags.isEmpty();
   }
 
+  @Override
   public boolean isLibraryCompilation() {
     return libraryCompilation;
   }
 
-  public AndroidApiLevel getRequiredCompilationAPILevel() {
-    return topLevelFlags.getRequiredCompilationAPILevel();
+  public MachineTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
   }
 
+  public MachineRewritingFlags getRewritingFlags() {
+    return rewritingFlags;
+  }
+
+  @Override
+  public AndroidApiLevel getRequiredCompilationApiLevel() {
+    return topLevelFlags.getRequiredCompilationApiLevel();
+  }
+
+  @Override
   public String getSynthesizedLibraryClassesPackagePrefix() {
     return topLevelFlags.getSynthesizedLibraryClassesPackagePrefix();
   }
@@ -72,6 +88,7 @@
     return topLevelFlags.getIdentifier();
   }
 
+  @Override
   public String getJsonSource() {
     return topLevelFlags.getJsonSource();
   }
@@ -80,6 +97,7 @@
     return topLevelFlags.supportAllCallbacksFromLibrary();
   }
 
+  @Override
   public List<String> getExtraKeepRules() {
     return topLevelFlags.getExtraKeepRules();
   }
@@ -204,8 +222,10 @@
     return false;
   }
 
-  public AndroidApiLevel getRequiredCompilationApiLevel() {
-    return topLevelFlags.getRequiredCompilationAPILevel();
+  @Override
+  public MachineDesugaredLibrarySpecification toMachineSpecification(
+      DexApplication app, Timing timing) throws IOException {
+    return this;
   }
 
   public boolean requiresTypeRewriting() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecificationParser.java
new file mode 100644
index 0000000..031ca29
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecificationParser.java
@@ -0,0 +1,479 @@
+// 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.ir.desugar.desugaredlibrary.machinespecification;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.CONFIGURATION_FORMAT_VERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.AMEND_LIBRARY_FIELD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.AMEND_LIBRARY_METHOD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_GENERIC_TYPES_CONVERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_LEVEL_BELOW_OR_EQUAL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_LEVEL_GREATER_OR_EQUAL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.COMMON_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.COVARIANT_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.CUSTOM_CONVERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.DONT_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_INTERFACE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_VIRTUAL_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.IDENTIFIER_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.LEGACY_BACKPORT_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.LIBRARY_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.MAINTAIN_TYPE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.NON_EMULATED_VIRTUAL_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.PACKAGE_MAP_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.PROGRAM_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REQUIRED_COMPILATION_API_LEVEL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REWRITE_DERIVED_TYPE_ONLY_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REWRITE_TYPE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SHRINKER_CONFIG_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.STATIC_FIELD_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.STATIC_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.WRAPPER_KEY;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.MachineFieldParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser.MachineMethodParser;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MachineDesugaredLibrarySpecificationParser {
+
+  private static final int MIN_SUPPORTED_VERSION = 200;
+  private static final int MAX_SUPPORTED_VERSION = 200;
+
+  private static final String ERROR_MESSAGE_PREFIX = "Invalid desugared library specification: ";
+
+  private final DexItemFactory dexItemFactory;
+  private final MachineMethodParser methodParser;
+  private final MachineFieldParser fieldParser;
+  private final Reporter reporter;
+  private final boolean libraryCompilation;
+  private final int minAPILevel;
+  private final SyntheticNaming syntheticNaming;
+
+  private Origin origin;
+  private JsonObject jsonConfig;
+  private Map<String, String> packageMap;
+
+  public MachineDesugaredLibrarySpecificationParser(
+      DexItemFactory dexItemFactory,
+      Reporter reporter,
+      boolean libraryCompilation,
+      int minAPILevel,
+      SyntheticNaming syntheticNaming) {
+    this.dexItemFactory = dexItemFactory;
+    this.methodParser = new MachineMethodParser(dexItemFactory, this::stringDescriptorToDexType);
+    this.fieldParser = new MachineFieldParser(dexItemFactory, this::stringDescriptorToDexType);
+    this.reporter = reporter;
+    this.minAPILevel = minAPILevel;
+    this.syntheticNaming = syntheticNaming;
+    this.libraryCompilation = libraryCompilation;
+  }
+
+  public DexItemFactory dexItemFactory() {
+    return dexItemFactory;
+  }
+
+  public Reporter reporter() {
+    return reporter;
+  }
+
+  public JsonObject getJsonConfig() {
+    return jsonConfig;
+  }
+
+  public Origin getOrigin() {
+    assert origin != null;
+    return origin;
+  }
+
+  JsonElement required(JsonObject json, String key) {
+    if (!json.has(key)) {
+      throw reporter.fatalError(
+          new StringDiagnostic(
+              "Invalid desugared library configuration. Expected required key '" + key + "'",
+              origin));
+    }
+    return json.get(key);
+  }
+
+  public MachineDesugaredLibrarySpecification parse(StringResource stringResource) {
+    String jsonConfigString = parseJson(stringResource);
+    return parse(origin, jsonConfigString, jsonConfig);
+  }
+
+  public MachineDesugaredLibrarySpecification parse(
+      Origin origin, String jsonConfigString, JsonObject jsonConfig) {
+    this.origin = origin;
+    this.jsonConfig = jsonConfig;
+    int machineVersion = required(jsonConfig, CONFIGURATION_FORMAT_VERSION_KEY).getAsInt();
+    if (machineVersion < MIN_SUPPORTED_VERSION || machineVersion > MAX_SUPPORTED_VERSION) {
+      throw reporter.fatalError(
+          new StringDiagnostic(
+              "Unsupported machine version number "
+                  + machineVersion
+                  + " not in ["
+                  + MIN_SUPPORTED_VERSION
+                  + ","
+                  + MAX_SUPPORTED_VERSION
+                  + "]",
+              origin));
+    }
+    MachineTopLevelFlags topLevelFlags = parseTopLevelFlags(jsonConfigString);
+    parsePackageMap();
+    MachineRewritingFlags rewritingFlags = parseRewritingFlags();
+    MachineDesugaredLibrarySpecification config =
+        new MachineDesugaredLibrarySpecification(libraryCompilation, topLevelFlags, rewritingFlags);
+    this.origin = null;
+    return config;
+  }
+
+  private void parsePackageMap() {
+    JsonObject packageMapJson = required(jsonConfig, PACKAGE_MAP_KEY).getAsJsonObject();
+    ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();
+    packageMapJson.entrySet().forEach((e) -> builder.put(e.getValue().getAsString(), e.getKey()));
+    packageMap = builder.build();
+  }
+
+  String parseJson(StringResource stringResource) {
+    setOrigin(stringResource);
+    String jsonConfigString;
+    try {
+      jsonConfigString = stringResource.getString();
+      JsonParser parser = new JsonParser();
+      jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
+    } catch (Exception e) {
+      throw reporter.fatalError(new ExceptionDiagnostic(e, origin));
+    }
+    return jsonConfigString;
+  }
+
+  void setOrigin(StringResource stringResource) {
+    origin = stringResource.getOrigin();
+    assert origin != null;
+  }
+
+  private MachineRewritingFlags parseRewritingFlags() {
+    MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
+    JsonElement commonFlags = required(jsonConfig, COMMON_FLAGS_KEY);
+    JsonElement libraryFlags = required(jsonConfig, LIBRARY_FLAGS_KEY);
+    JsonElement programFlags = required(jsonConfig, PROGRAM_FLAGS_KEY);
+    parseFlagsList(commonFlags.getAsJsonArray(), builder);
+    parseFlagsList(
+        libraryCompilation ? libraryFlags.getAsJsonArray() : programFlags.getAsJsonArray(),
+        builder);
+    return builder.build();
+  }
+
+  MachineTopLevelFlags parseTopLevelFlags(String jsonConfigString) {
+    String identifier = required(jsonConfig, IDENTIFIER_KEY).getAsString();
+    String prefix =
+        required(jsonConfig, SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY).getAsString();
+    int required_compilation_api_level =
+        required(jsonConfig, REQUIRED_COMPILATION_API_LEVEL_KEY).getAsInt();
+    String keepRules = required(jsonConfig, SHRINKER_CONFIG_KEY).getAsString();
+    boolean supportAllCallbacksFromLibrary =
+        jsonConfig.get(SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY).getAsBoolean();
+
+    return new MachineTopLevelFlags(
+        AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level),
+        prefix,
+        identifier,
+        jsonConfigString,
+        supportAllCallbacksFromLibrary,
+        ImmutableList.of(keepRules));
+  }
+
+  private void parseFlagsList(JsonArray jsonFlags, MachineRewritingFlags.Builder builder) {
+    for (JsonElement jsonFlagSet : jsonFlags) {
+      JsonObject flag = jsonFlagSet.getAsJsonObject();
+      int api_level_below_or_equal = required(flag, API_LEVEL_BELOW_OR_EQUAL_KEY).getAsInt();
+      if (minAPILevel <= api_level_below_or_equal) {
+        if (flag.has(API_LEVEL_GREATER_OR_EQUAL_KEY)) {
+          if (minAPILevel >= flag.get(API_LEVEL_GREATER_OR_EQUAL_KEY).getAsInt()) {
+            parseFlags(flag, builder);
+          }
+        } else {
+          parseFlags(flag, builder);
+        }
+      }
+    }
+  }
+
+  void parseFlags(JsonObject jsonFlagSet, MachineRewritingFlags.Builder builder) {
+    if (jsonFlagSet.has(REWRITE_TYPE_KEY)) {
+      for (Map.Entry<String, JsonElement> rewritePrefix :
+          jsonFlagSet.get(REWRITE_TYPE_KEY).getAsJsonObject().entrySet()) {
+        builder.rewriteType(
+            stringDescriptorToDexType(rewritePrefix.getKey()),
+            stringDescriptorToDexType(rewritePrefix.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(MAINTAIN_TYPE_KEY)) {
+      for (JsonElement maintainPrefix : jsonFlagSet.get(MAINTAIN_TYPE_KEY).getAsJsonArray()) {
+        builder.maintainType(stringDescriptorToDexType(maintainPrefix.getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(REWRITE_DERIVED_TYPE_ONLY_KEY)) {
+      for (Map.Entry<String, JsonElement> rewriteDerivedTypeOnly :
+          jsonFlagSet.get(REWRITE_DERIVED_TYPE_ONLY_KEY).getAsJsonObject().entrySet()) {
+        builder.rewriteDerivedTypeOnly(
+            stringDescriptorToDexType(rewriteDerivedTypeOnly.getKey()),
+            stringDescriptorToDexType(rewriteDerivedTypeOnly.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(STATIC_FIELD_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> staticFieldRetarget :
+          jsonFlagSet.get(STATIC_FIELD_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putStaticFieldRetarget(
+            parseField(staticFieldRetarget.getKey()),
+            parseField(staticFieldRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(COVARIANT_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> covariantRetarget :
+          jsonFlagSet.get(COVARIANT_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putCovariantRetarget(
+            parseMethod(covariantRetarget.getKey()),
+            parseMethod(covariantRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(STATIC_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> staticRetarget :
+          jsonFlagSet.get(STATIC_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putStaticRetarget(
+            parseMethod(staticRetarget.getKey()),
+            parseMethod(staticRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(NON_EMULATED_VIRTUAL_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> virtualRetarget :
+          jsonFlagSet.get(NON_EMULATED_VIRTUAL_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putNonEmulatedVirtualRetarget(
+            parseMethod(virtualRetarget.getKey()),
+            parseMethod(virtualRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(EMULATED_VIRTUAL_RETARGET_KEY)) {
+      for (Map.Entry<String, JsonElement> virtualRetarget :
+          jsonFlagSet.get(EMULATED_VIRTUAL_RETARGET_KEY).getAsJsonObject().entrySet()) {
+        builder.putEmulatedVirtualRetarget(
+            parseMethod(virtualRetarget.getKey()),
+            parseEmulatedDispatchDescriptor(virtualRetarget.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY)) {
+      for (Map.Entry<String, JsonElement> virtualRetarget :
+          jsonFlagSet
+              .get(EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY)
+              .getAsJsonObject()
+              .entrySet()) {
+        builder.putEmulatedVirtualRetargetThroughEmulatedInterface(
+            parseMethod(virtualRetarget.getKey()),
+            parseMethod(virtualRetarget.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(API_GENERIC_TYPES_CONVERSION_KEY)) {
+      for (Map.Entry<String, JsonElement> apiGenericType :
+          jsonFlagSet.get(API_GENERIC_TYPES_CONVERSION_KEY).getAsJsonObject().entrySet()) {
+        builder.addApiGenericTypesConversion(
+            parseMethod(apiGenericType.getKey()),
+            parseMethodArray(apiGenericType.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(EMULATED_INTERFACE_KEY)) {
+      for (Map.Entry<String, JsonElement> emulatedInterface :
+          jsonFlagSet.get(EMULATED_INTERFACE_KEY).getAsJsonObject().entrySet()) {
+        builder.putEmulatedInterface(
+            stringDescriptorToDexType(emulatedInterface.getKey()),
+            parseEmulatedInterfaceDescriptor(emulatedInterface.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(WRAPPER_KEY)) {
+      for (Map.Entry<String, JsonElement> wrapper :
+          jsonFlagSet.get(WRAPPER_KEY).getAsJsonObject().entrySet()) {
+        builder.addWrapper(
+            stringDescriptorToDexType(wrapper.getKey()),
+            parseWrapperDescriptor(wrapper.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(LEGACY_BACKPORT_KEY)) {
+      for (Map.Entry<String, JsonElement> legacyBackport :
+          jsonFlagSet.get(LEGACY_BACKPORT_KEY).getAsJsonObject().entrySet()) {
+        builder.putLegacyBackport(
+            stringDescriptorToDexType(legacyBackport.getKey()),
+            stringDescriptorToDexType(legacyBackport.getValue().getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(DONT_RETARGET_KEY)) {
+      for (JsonElement dontRetarget : jsonFlagSet.get(DONT_RETARGET_KEY).getAsJsonArray()) {
+        builder.addDontRetarget(stringDescriptorToDexType(dontRetarget.getAsString()));
+      }
+    }
+    if (jsonFlagSet.has(CUSTOM_CONVERSION_KEY)) {
+      for (Map.Entry<String, JsonElement> customConversion :
+          jsonFlagSet.get(CUSTOM_CONVERSION_KEY).getAsJsonObject().entrySet()) {
+        builder.putCustomConversion(
+            stringDescriptorToDexType(customConversion.getKey()),
+            parseCustomConversionDescriptor(customConversion.getValue().getAsJsonArray()));
+      }
+    }
+    if (jsonFlagSet.has(AMEND_LIBRARY_METHOD_KEY)) {
+      JsonArray amendLibraryMember = jsonFlagSet.get(AMEND_LIBRARY_METHOD_KEY).getAsJsonArray();
+      for (JsonElement amend : amendLibraryMember) {
+        methodParser.parseMethod(amend.getAsString());
+        builder.amendLibraryMethod(methodParser.getMethod(), methodParser.getFlags());
+      }
+    }
+    if (jsonFlagSet.has(AMEND_LIBRARY_FIELD_KEY)) {
+      JsonArray amendLibraryMember = jsonFlagSet.get(AMEND_LIBRARY_FIELD_KEY).getAsJsonArray();
+      for (JsonElement amend : amendLibraryMember) {
+        fieldParser.parseField(amend.getAsString());
+        builder.amendLibraryField(fieldParser.getField(), fieldParser.getFlags());
+      }
+    }
+  }
+
+  private CustomConversionDescriptor parseCustomConversionDescriptor(JsonArray jsonArray) {
+    return new CustomConversionDescriptor(
+        parseMethod(jsonArray.get(0).getAsString()), parseMethod(jsonArray.get(1).getAsString()));
+  }
+
+  private WrapperDescriptor parseWrapperDescriptor(JsonArray jsonArray) {
+    List<DexMethod> methods = parseMethodList(jsonArray.get(0).getAsJsonArray());
+    boolean nonPublicAccess = jsonArray.get(1).getAsBoolean();
+    List<DexType> subwrappers = parseTypeList(jsonArray.get(2).getAsJsonArray());
+    return new WrapperDescriptor(methods, subwrappers, nonPublicAccess);
+  }
+
+  private void require(JsonArray jsonArray, int size, String elementString) {
+    if (jsonArray.size() != size) {
+      throw reporter.fatalError(
+          ERROR_MESSAGE_PREFIX + elementString + "(Json array of size " + jsonArray.size() + ")");
+    }
+  }
+
+  private EmulatedInterfaceDescriptor parseEmulatedInterfaceDescriptor(JsonArray jsonArray) {
+    require(jsonArray, 2, "emulated interface descriptor");
+    DexType rewrittenType = stringDescriptorToDexType(jsonArray.get(0).getAsString());
+    Map<DexMethod, EmulatedDispatchMethodDescriptor> methods =
+        parseEmulatedInterfaceMap(jsonArray.get(1).getAsJsonObject());
+    return new EmulatedInterfaceDescriptor(rewrittenType, methods);
+  }
+
+  private Map<DexMethod, EmulatedDispatchMethodDescriptor> parseEmulatedInterfaceMap(
+      JsonObject jsonObject) {
+    Map<DexMethod, EmulatedDispatchMethodDescriptor> map = new IdentityHashMap<>();
+    for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
+      map.put(
+          parseMethod(entry.getKey()),
+          parseEmulatedDispatchDescriptor(entry.getValue().getAsJsonArray()));
+    }
+    return map;
+  }
+
+  private LinkedHashMap<DexType, DerivedMethod> parseEmulatedDispatchMap(JsonObject jsonObject) {
+    LinkedHashMap<DexType, DerivedMethod> map = new LinkedHashMap<>();
+    for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
+      map.put(
+          stringDescriptorToDexType(entry.getKey()),
+          parseDerivedMethod(entry.getValue().getAsJsonArray()));
+    }
+    return map;
+  }
+
+  private EmulatedDispatchMethodDescriptor parseEmulatedDispatchDescriptor(JsonArray jsonArray) {
+    require(jsonArray, 4, "emulated dispatch descriptor");
+    DerivedMethod interfaceMethod = parseDerivedMethod(jsonArray.get(0).getAsJsonArray());
+    DerivedMethod emulatedDispatchMethod = parseDerivedMethod(jsonArray.get(1).getAsJsonArray());
+    DerivedMethod forwardingMethod = parseDerivedMethod(jsonArray.get(2).getAsJsonArray());
+    LinkedHashMap<DexType, DerivedMethod> dispatchCases =
+        parseEmulatedDispatchMap(jsonArray.get(3).getAsJsonObject());
+    return new EmulatedDispatchMethodDescriptor(
+        interfaceMethod, emulatedDispatchMethod, forwardingMethod, dispatchCases);
+  }
+
+  private DerivedMethod parseDerivedMethod(JsonArray jsonArray) {
+    require(jsonArray, 2, "derived method");
+    DexMethod dexMethod = parseMethod(jsonArray.get(0).getAsString());
+    int kind = jsonArray.get(1).getAsInt();
+    if (kind == -1) {
+      return new DerivedMethod(dexMethod);
+    }
+    SyntheticKind syntheticKind = syntheticNaming.fromId(kind);
+    return new DerivedMethod(dexMethod, syntheticKind);
+  }
+
+  private List<DexMethod> parseMethodList(JsonArray array) {
+    List<DexMethod> methods = new ArrayList<>();
+    for (JsonElement method : array) {
+      methods.add(parseMethod(method.getAsString()));
+    }
+    return methods;
+  }
+
+  private List<DexType> parseTypeList(JsonArray array) {
+    List<DexType> types = new ArrayList<>();
+    for (JsonElement typeString : array) {
+      types.add(stringDescriptorToDexType(typeString.getAsString()));
+    }
+    return types;
+  }
+
+  private DexMethod[] parseMethodArray(JsonArray array) {
+    DexMethod[] dexMethods = new DexMethod[array.size()];
+    for (int i = 0; i < array.size(); i++) {
+      String str = array.get(i).getAsString();
+      dexMethods[i] = str.isEmpty() ? null : parseMethod(str);
+    }
+    return dexMethods;
+  }
+
+  private DexMethod parseMethod(String signature) {
+    methodParser.parseMethod(signature);
+    return methodParser.getMethod();
+  }
+
+  private DexField parseField(String signature) {
+    fieldParser.parseField(signature);
+    return fieldParser.getField();
+  }
+
+  public DexType stringDescriptorToDexType(String stringClass) {
+    if (stringClass.charAt(1) != '$') {
+      return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass));
+    }
+    String type = stringClass.substring(2);
+    String prefix = packageMap.get(stringClass.substring(0, 2));
+    if (prefix == null) {
+      throw reporter.fatalError(
+          ERROR_MESSAGE_PREFIX + "Missing package mapping for " + stringClass.substring(0, 2));
+    }
+    return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(prefix + "." + type));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 5ad734b..523042f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -159,7 +159,7 @@
     return emulatedInterfaces;
   }
 
-  public Map<DexType, WrapperDescriptor> getWrappers() {
+  public LinkedHashMap<DexType, WrapperDescriptor> getWrappers() {
     return wrappers;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineSpecificationJsonPool.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineSpecificationJsonPool.java
new file mode 100644
index 0000000..105a18b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineSpecificationJsonPool.java
@@ -0,0 +1,43 @@
+// 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.ir.desugar.desugaredlibrary.machinespecification;
+
+public class MachineSpecificationJsonPool {
+
+  static final String REQUIRED_COMPILATION_API_LEVEL_KEY = "required_compilation_api_level";
+  static final String SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY =
+      "synthesized_library_classes_package_prefix";
+  static final String IDENTIFIER_KEY = "identifier";
+  static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library";
+  static final String SHRINKER_CONFIG_KEY = "shrinker_config";
+
+  static final String COMMON_FLAGS_KEY = "common_flags";
+  static final String LIBRARY_FLAGS_KEY = "library_flags";
+  static final String PROGRAM_FLAGS_KEY = "program_flags";
+
+  static final String API_LEVEL_BELOW_OR_EQUAL_KEY = "api_level_below_or_equal";
+  static final String API_LEVEL_GREATER_OR_EQUAL_KEY = "api_level_greater_or_equal";
+
+  static final String REWRITE_TYPE_KEY = "rewrite_type";
+  static final String MAINTAIN_TYPE_KEY = "maintain_type";
+  static final String REWRITE_DERIVED_TYPE_ONLY_KEY = "rewrite_derived_type_only";
+  static final String STATIC_FIELD_RETARGET_KEY = "static_field_retarget";
+  static final String COVARIANT_RETARGET_KEY = "covariant_retarget";
+  static final String STATIC_RETARGET_KEY = "static_retarget";
+  static final String NON_EMULATED_VIRTUAL_RETARGET_KEY = "non_emulated_virtual_retarget";
+  static final String EMULATED_VIRTUAL_RETARGET_KEY = "emulated_virtual_retarget";
+  static final String EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY =
+      "emulated_virtual_retarget_through_emulated_interface";
+  static final String API_GENERIC_TYPES_CONVERSION_KEY = "api_generic_types_conversion";
+  static final String EMULATED_INTERFACE_KEY = "emulated_interface";
+  static final String WRAPPER_KEY = "wrapper";
+  static final String LEGACY_BACKPORT_KEY = "legacy_backport";
+  static final String DONT_RETARGET_KEY = "dont_retarget";
+  static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
+  static final String AMEND_LIBRARY_METHOD_KEY = "amend_library_method";
+  static final String AMEND_LIBRARY_FIELD_KEY = "amend_library_field";
+
+  static final String PACKAGE_MAP_KEY = "package_map";
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineTopLevelFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineTopLevelFlags.java
index 0c6a88a..c5c1b4b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineTopLevelFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineTopLevelFlags.java
@@ -44,7 +44,7 @@
     this.extraKeepRules = extraKeepRules;
   }
 
-  public AndroidApiLevel getRequiredCompilationAPILevel() {
+  public AndroidApiLevel getRequiredCompilationApiLevel() {
     return requiredCompilationAPILevel;
   }
 
@@ -67,4 +67,8 @@
   public List<String> getExtraKeepRules() {
     return extraKeepRules;
   }
+
+  public String getExtraKeepRulesConcatenated() {
+    return String.join("\n", extraKeepRules);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecification.java
new file mode 100644
index 0000000..04ddb16
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecification.java
@@ -0,0 +1,51 @@
+// 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.ir.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.android.tools.r8.origin.Origin;
+import java.util.Map;
+
+public class MultiAPILevelMachineDesugaredLibrarySpecification {
+
+  private final Origin origin;
+  private final MachineTopLevelFlags topLevelFlags;
+  private final Map<ApiLevelRange, MachineRewritingFlags> commonFlags;
+  private final Map<ApiLevelRange, MachineRewritingFlags> libraryFlags;
+  private final Map<ApiLevelRange, MachineRewritingFlags> programFlags;
+
+  public MultiAPILevelMachineDesugaredLibrarySpecification(
+      Origin origin,
+      MachineTopLevelFlags topLevelFlags,
+      Map<ApiLevelRange, MachineRewritingFlags> commonFlags,
+      Map<ApiLevelRange, MachineRewritingFlags> libraryFlags,
+      Map<ApiLevelRange, MachineRewritingFlags> programFlags) {
+    this.origin = origin;
+    this.topLevelFlags = topLevelFlags;
+    this.commonFlags = commonFlags;
+    this.libraryFlags = libraryFlags;
+    this.programFlags = programFlags;
+  }
+
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  public MachineTopLevelFlags getTopLevelFlags() {
+    return topLevelFlags;
+  }
+
+  public Map<ApiLevelRange, MachineRewritingFlags> getCommonFlags() {
+    return commonFlags;
+  }
+
+  public Map<ApiLevelRange, MachineRewritingFlags> getLibraryFlags() {
+    return libraryFlags;
+  }
+
+  public Map<ApiLevelRange, MachineRewritingFlags> getProgramFlags() {
+    return programFlags;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
new file mode 100644
index 0000000..f5d81fa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
@@ -0,0 +1,350 @@
+// 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.ir.desugar.desugaredlibrary.machinespecification;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.CONFIGURATION_FORMAT_VERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.AMEND_LIBRARY_FIELD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.AMEND_LIBRARY_METHOD_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_GENERIC_TYPES_CONVERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_LEVEL_BELOW_OR_EQUAL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.API_LEVEL_GREATER_OR_EQUAL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.COMMON_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.COVARIANT_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.CUSTOM_CONVERSION_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.DONT_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_INTERFACE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_VIRTUAL_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.IDENTIFIER_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.LEGACY_BACKPORT_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.LIBRARY_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.MAINTAIN_TYPE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.NON_EMULATED_VIRTUAL_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.PACKAGE_MAP_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.PROGRAM_FLAGS_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REQUIRED_COMPILATION_API_LEVEL_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REWRITE_DERIVED_TYPE_ONLY_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.REWRITE_TYPE_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SHRINKER_CONFIG_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.STATIC_FIELD_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.STATIC_RETARGET_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineSpecificationJsonPool.WRAPPER_KEY;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.google.gson.Gson;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter {
+
+  private static final int MACHINE_VERSION_NUMBER = 200;
+
+  private final DexItemFactory factory;
+  private final Map<String, String> packageMap = new TreeMap<>();
+  private static final String chars =
+      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789æÆøØ";
+  private int next = 0;
+
+  public MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter(DexItemFactory factory) {
+    this.factory = factory;
+  }
+
+  public static void export(
+      MultiAPILevelMachineDesugaredLibrarySpecification specification,
+      StringConsumer output,
+      DexItemFactory factory) {
+    new MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter(factory)
+        .internalExport(specification, output);
+  }
+
+  private void internalExport(
+      MultiAPILevelMachineDesugaredLibrarySpecification machineSpec, StringConsumer output) {
+    HashMap<String, Object> toJson = new LinkedHashMap<>();
+
+    exportTopLevelFlags(machineSpec.getTopLevelFlags(), toJson);
+    toJson.put(CONFIGURATION_FORMAT_VERSION_KEY, MACHINE_VERSION_NUMBER);
+
+    toJson.put(COMMON_FLAGS_KEY, rewritingFlagsToString(machineSpec.getCommonFlags()));
+    toJson.put(PROGRAM_FLAGS_KEY, rewritingFlagsToString(machineSpec.getProgramFlags()));
+    toJson.put(LIBRARY_FLAGS_KEY, rewritingFlagsToString(machineSpec.getLibraryFlags()));
+
+    toJson.put(PACKAGE_MAP_KEY, packageMap);
+
+    Gson gson = new Gson();
+    String export = gson.toJson(toJson);
+    output.accept(export, new DiagnosticsHandler() {});
+  }
+
+  private void exportTopLevelFlags(MachineTopLevelFlags topLevelFlags, Map<String, Object> toJson) {
+    toJson.put(IDENTIFIER_KEY, topLevelFlags.getIdentifier());
+    toJson.put(
+        REQUIRED_COMPILATION_API_LEVEL_KEY,
+        topLevelFlags.getRequiredCompilationApiLevel().getLevel());
+    toJson.put(
+        SYNTHESIZED_LIBRARY_CLASSES_PACKAGE_PREFIX_KEY,
+        topLevelFlags.getSynthesizedLibraryClassesPackagePrefix());
+    toJson.put(
+        SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY, topLevelFlags.supportAllCallbacksFromLibrary());
+    toJson.put(SHRINKER_CONFIG_KEY, topLevelFlags.getExtraKeepRulesConcatenated());
+  }
+
+  private List<Object> rewritingFlagsToString(
+      Map<ApiLevelRange, MachineRewritingFlags> rewritingFlagsMap) {
+    ArrayList<Object> list = new ArrayList<>();
+    ArrayList<ApiLevelRange> apis = new ArrayList<>(rewritingFlagsMap.keySet());
+    apis.sort((x, y) -> -x.deterministicOrder(y));
+    for (ApiLevelRange range : apis) {
+      MachineRewritingFlags flags = rewritingFlagsMap.get(range);
+      HashMap<String, Object> toJson = new LinkedHashMap<>();
+      toJson.put(API_LEVEL_BELOW_OR_EQUAL_KEY, range.getApiLevelBelowOrEqualAsInt());
+      if (range.hasApiLevelGreaterOrEqual()) {
+        toJson.put(API_LEVEL_GREATER_OR_EQUAL_KEY, range.getApiLevelGreaterOrEqualAsInt());
+      }
+      writeFlags(flags, toJson);
+      list.add(toJson);
+    }
+    return list;
+  }
+
+  private void writeFlagCollection(
+      String key, Collection<? extends DexItem> collection, Map<String, Object> toJson) {
+    if (!collection.isEmpty()) {
+      toJson.put(key, collectionToJsonStruct(collection));
+    }
+  }
+
+  private void writeFlagMap(
+      String key, Map<? extends DexItem, ? extends DexItem> map, Map<String, Object> toJson) {
+    if (!map.isEmpty()) {
+      toJson.put(key, mapToJsonStruct(map));
+    }
+  }
+
+  private void writeFlagMapToSpecificationDescriptor(
+      String key,
+      Map<? extends DexItem, ? extends SpecificationDescriptor> map,
+      Map<String, Object> toJson) {
+    if (!map.isEmpty()) {
+      toJson.put(key, specificationDescriptorMapToJsonStruct(map));
+    }
+  }
+
+  private void writeFlagMapToMethodArray(
+      String key, Map<? extends DexItem, DexMethod[]> map, Map<String, Object> toJson) {
+    if (!map.isEmpty()) {
+      TreeMap<String, Object> stringMap = new TreeMap<>();
+      map.forEach((k, v) -> stringMap.put(toString(k), methodArrayToJsonStruct(v)));
+      toJson.put(key, stringMap);
+    }
+  }
+
+  private void writeFlagLinkedHashMapToSpecificationDescriptor(
+      String key,
+      LinkedHashMap<? extends DexItem, ? extends SpecificationDescriptor> map,
+      Map<String, Object> toJson) {
+    if (!map.isEmpty()) {
+      toJson.put(key, specificationDescriptorLinkedHashMapToJsonStruct(map));
+    }
+  }
+
+  private void writeMembersWithFlags(
+      String key,
+      Map<? extends DexItem, ? extends AccessFlags<?>> membersWithFlags,
+      Map<String, Object> toJson) {
+    if (!membersWithFlags.isEmpty()) {
+      List<String> stringSet = new ArrayList<>();
+      membersWithFlags.forEach(
+          (member, flags) -> stringSet.add(flags.toString() + " " + toString(member)));
+      toJson.put(key, stringSet);
+    }
+  }
+
+  private void writeFlags(MachineRewritingFlags flags, Map<String, Object> toJson) {
+    writeFlagMap(REWRITE_TYPE_KEY, flags.getRewriteType(), toJson);
+    writeFlagCollection(MAINTAIN_TYPE_KEY, flags.getMaintainType(), toJson);
+    writeFlagMap(REWRITE_DERIVED_TYPE_ONLY_KEY, flags.getRewriteDerivedTypeOnly(), toJson);
+    writeFlagMap(STATIC_FIELD_RETARGET_KEY, flags.getStaticFieldRetarget(), toJson);
+    writeFlagMap(COVARIANT_RETARGET_KEY, flags.getCovariantRetarget(), toJson);
+    writeFlagMap(STATIC_RETARGET_KEY, flags.getStaticRetarget(), toJson);
+    writeFlagMap(NON_EMULATED_VIRTUAL_RETARGET_KEY, flags.getNonEmulatedVirtualRetarget(), toJson);
+    writeFlagMapToSpecificationDescriptor(
+        EMULATED_VIRTUAL_RETARGET_KEY, flags.getEmulatedVirtualRetarget(), toJson);
+    writeFlagMap(
+        EMULATED_VIRTUAL_RETARGET_THROUGH_EMULATED_INTERFACE_KEY,
+        flags.getEmulatedVirtualRetargetThroughEmulatedInterface(),
+        toJson);
+    writeFlagMapToMethodArray(
+        API_GENERIC_TYPES_CONVERSION_KEY, flags.getApiGenericConversion(), toJson);
+    writeFlagMapToSpecificationDescriptor(
+        EMULATED_INTERFACE_KEY, flags.getEmulatedInterfaces(), toJson);
+    writeFlagLinkedHashMapToSpecificationDescriptor(WRAPPER_KEY, flags.getWrappers(), toJson);
+    writeFlagMap(LEGACY_BACKPORT_KEY, flags.getLegacyBackport(), toJson);
+    writeFlagCollection(DONT_RETARGET_KEY, flags.getDontRetarget(), toJson);
+    writeFlagMapToSpecificationDescriptor(
+        CUSTOM_CONVERSION_KEY, flags.getCustomConversions(), toJson);
+    writeMembersWithFlags(AMEND_LIBRARY_METHOD_KEY, flags.getAmendLibraryMethod(), toJson);
+    writeMembersWithFlags(AMEND_LIBRARY_FIELD_KEY, flags.getAmendLibraryField(), toJson);
+  }
+
+  private LinkedHashMap<String, ?> specificationDescriptorLinkedHashMapToJsonStruct(
+      LinkedHashMap<? extends DexItem, ? extends SpecificationDescriptor> map) {
+    // Already sorted with custom advanced deterministic sort, maintain the order.
+    LinkedHashMap<String, Object> stringMap = new LinkedHashMap<>();
+    map.forEach((k, v) -> stringMap.put(toString(k), v.toJsonStruct(this)));
+    return stringMap;
+  }
+
+  private TreeMap<String, String> mapToJsonStruct(Map<? extends DexItem, ? extends DexItem> map) {
+    TreeMap<String, String> stringMap = new TreeMap<>();
+    map.forEach((k, v) -> stringMap.put(toString(k), toString(v)));
+    return stringMap;
+  }
+
+  private TreeMap<String, ?> specificationDescriptorMapToJsonStruct(
+      Map<? extends DexItem, ? extends SpecificationDescriptor> map) {
+    TreeMap<String, Object> stringMap = new TreeMap<>();
+    map.forEach((k, v) -> stringMap.put(toString(k), v.toJsonStruct(this)));
+    return stringMap;
+  }
+
+  private List<String> collectionToJsonStruct(Collection<? extends DexItem> col) {
+    List<String> stringCol = new ArrayList<>();
+    col.forEach(e -> stringCol.add(toString(e)));
+    stringCol.sort(Comparator.naturalOrder());
+    return stringCol;
+  }
+
+  private String[] methodArrayToJsonStruct(DexMethod[] methodArray) {
+    String[] strings = new String[methodArray.length];
+    for (int i = 0; i < methodArray.length; i++) {
+      strings[i] = methodArray[i] == null ? "" : toString(methodArray[i]);
+    }
+    return strings;
+  }
+
+  private String toString(DexItem o) {
+    if (o instanceof DexType) {
+      return typeToString((DexType) o);
+    }
+    if (o instanceof DexField) {
+      DexField field = (DexField) o;
+      return typeToString(field.getType())
+          + " "
+          + typeToString(field.getHolderType())
+          + "#"
+          + field.getName();
+    }
+    if (o instanceof DexMethod) {
+      DexMethod method = (DexMethod) o;
+      StringBuilder sb =
+          new StringBuilder()
+              .append(typeToString(method.getReturnType()))
+              .append(" ")
+              .append(typeToString(method.getHolderType()))
+              .append("#")
+              .append(method.getName())
+              .append("(");
+      for (int i = 0; i < method.getParameters().size(); i++) {
+        sb.append(typeToString(method.getParameter(i)));
+        if (i != method.getParameters().size() - 1) {
+          sb.append(", ");
+        }
+      }
+      sb.append(")");
+      return sb.toString();
+    }
+    throw new Unreachable();
+  }
+
+  private String typeToString(DexType type) {
+    if (type.isPrimitiveType() || type.isPrimitiveArrayType() || type.isVoidType()) {
+      return type.toString();
+    }
+    if (type.isArrayType()) {
+      StringBuilder sb = new StringBuilder();
+      sb.append(typeToString(type.toBaseType(factory)));
+      for (int i = 0; i < type.getNumberOfLeadingSquareBrackets(); i++) {
+        sb.append("[]");
+      }
+      return sb.toString();
+    }
+    String pack =
+        packageMap.computeIfAbsent(type.getPackageName(), k -> nextMinifiedPackagePrefix());
+    return pack + type.getSimpleName();
+  }
+
+  private String nextMinifiedPackagePrefix() {
+    if (next >= chars.length()) {
+      // This should happen only when the R8 team release machine specifications (not in user
+      // compilations).
+      throw new RuntimeException(
+          "MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter "
+              + "cannot encode the next package because the encoding ran out of characters."
+              + " Extend the chars sequence or improve the encoding to fix this.");
+    }
+    return chars.charAt(next++) + "$";
+  }
+
+  public Object[] exportCustomConversionDescriptor(
+      CustomConversionDescriptor customConversionDescriptor) {
+    String toString = toString(customConversionDescriptor.getTo());
+    String fromString = toString(customConversionDescriptor.getFrom());
+    return new Object[] {toString, fromString};
+  }
+
+  public Object[] exportDerivedMethod(DerivedMethod derivedMethod) {
+    String methodString = toString(derivedMethod.getMethod());
+    String holderKindString =
+        Integer.toString(
+            derivedMethod.getHolderKind() == null ? -1 : derivedMethod.getHolderKind().getId());
+    return new Object[] {methodString, holderKindString};
+  }
+
+  public Object[] exportEmulatedDispatchMethodDescriptor(
+      EmulatedDispatchMethodDescriptor descriptor) {
+    Object interfaceMethodJsonStruct = exportDerivedMethod(descriptor.getInterfaceMethod());
+    Object emulatedDispatchMethodJsonStruct =
+        exportDerivedMethod(descriptor.getEmulatedDispatchMethod());
+    Object forwardingMethodJsonStruct = exportDerivedMethod(descriptor.getForwardingMethod());
+    Object dispatchCasesJsonStruct =
+        specificationDescriptorLinkedHashMapToJsonStruct(descriptor.getDispatchCases());
+    return new Object[] {
+      interfaceMethodJsonStruct,
+      emulatedDispatchMethodJsonStruct,
+      forwardingMethodJsonStruct,
+      dispatchCasesJsonStruct
+    };
+  }
+
+  public Object[] exportEmulatedInterfaceDescriptor(EmulatedInterfaceDescriptor descriptor) {
+    Object rewrittenTypeString = toString(descriptor.getRewrittenType());
+    Object emulatedMethodsJsonStruct =
+        specificationDescriptorMapToJsonStruct(descriptor.getEmulatedMethods());
+    return new Object[] {rewrittenTypeString, emulatedMethodsJsonStruct};
+  }
+
+  public Object[] exportWrapperDescriptor(WrapperDescriptor descriptor) {
+    Object methodStruct = collectionToJsonStruct(descriptor.getMethods());
+    Object subwrappersStruct = collectionToJsonStruct(descriptor.getSubwrappers());
+    return new Object[] {methodStruct, descriptor.hasNonPublicAccess(), subwrappersStruct};
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/SpecificationDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/SpecificationDescriptor.java
new file mode 100644
index 0000000..0d37b32
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/SpecificationDescriptor.java
@@ -0,0 +1,10 @@
+// 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.ir.desugar.desugaredlibrary.machinespecification;
+
+public interface SpecificationDescriptor {
+
+  Object[] toJsonStruct(MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
index 2403b90..156547e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
@@ -7,8 +7,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import java.util.List;
+import java.util.Objects;
 
-public class WrapperDescriptor {
+public class WrapperDescriptor implements SpecificationDescriptor {
   private final List<DexMethod> methods;
   private final List<DexType> subwrappers;
   private final boolean nonPublicAccess;
@@ -31,4 +32,29 @@
   public boolean hasNonPublicAccess() {
     return nonPublicAccess;
   }
+
+  @Override
+  public Object[] toJsonStruct(
+      MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
+    return exporter.exportWrapperDescriptor(this);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof WrapperDescriptor)) {
+      return false;
+    }
+    WrapperDescriptor that = (WrapperDescriptor) o;
+    return nonPublicAccess == that.nonPublicAccess
+        && Objects.equals(methods, that.methods)
+        && Objects.equals(subwrappers, that.subwrappers);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(methods, subwrappers, nonPublicAccess);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractFieldParser.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractFieldParser.java
index 8062063..ba3a060 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractFieldParser.java
@@ -2,7 +2,7 @@
 // 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.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMemberParser.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMemberParser.java
index 99475b2..1848ed8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMemberParser.java
@@ -2,7 +2,7 @@
 // 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.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMethodParser.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMethodParser.java
index 1afff88..c10882d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/AbstractMethodParser.java
@@ -2,7 +2,7 @@
 // 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.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanFieldParser.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanFieldParser.java
index 9a5a44d..7f1b03f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanFieldParser.java
@@ -2,7 +2,7 @@
 // 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.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanMethodParser.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanMethodParser.java
index d78267b..b7bfd33 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/HumanMethodParser.java
@@ -2,7 +2,7 @@
 // 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.humanspecification.memberparser;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.memberparser;
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineFieldParser.java
new file mode 100644
index 0000000..002a8d3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineFieldParser.java
@@ -0,0 +1,23 @@
+// 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.ir.desugar.desugaredlibrary.memberparser;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import java.util.function.Function;
+
+public class MachineFieldParser extends HumanFieldParser {
+  private final Function<String, DexType> typeParser;
+
+  public MachineFieldParser(DexItemFactory factory, Function<String, DexType> typeParser) {
+    super(factory);
+    this.typeParser = typeParser;
+  }
+
+  @Override
+  DexType stringTypeToDexType(String stringType) {
+    return typeParser.apply(stringType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineMethodParser.java
new file mode 100644
index 0000000..6b88d87
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/memberparser/MachineMethodParser.java
@@ -0,0 +1,23 @@
+// 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.ir.desugar.desugaredlibrary.memberparser;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import java.util.function.Function;
+
+public class MachineMethodParser extends HumanMethodParser {
+  private final Function<String, DexType> typeParser;
+
+  public MachineMethodParser(DexItemFactory factory, Function<String, DexType> typeParser) {
+    super(factory);
+    this.typeParser = typeParser;
+  }
+
+  @Override
+  DexType stringTypeToDexType(String stringType) {
+    return typeParser.apply(stringType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/AppForSpecConversion.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/AppForSpecConversion.java
deleted file mode 100644
index 74c31b4..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/AppForSpecConversion.java
+++ /dev/null
@@ -1,51 +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.ir.desugar.desugaredlibrary.specificationconversion;
-
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.DexApplication;
-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 java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.concurrent.ExecutorService;
-
-public class AppForSpecConversion {
-
-  public static DexApplication readAppForTesting(
-      Collection<Path> desugaredJDKLib,
-      Collection<Path> androidLib,
-      InternalOptions options,
-      boolean libraryCompilation,
-      Timing timing)
-      throws IOException {
-    timing.begin("Read App for testing");
-    assert !libraryCompilation || desugaredJDKLib != null;
-    AndroidApp.Builder builder = AndroidApp.builder();
-    if (libraryCompilation) {
-      builder.addProgramFiles(desugaredJDKLib);
-    }
-    AndroidApp inputApp = builder.addLibraryFiles(androidLib).build();
-    DexApplication app = internalReadApp(inputApp, options, timing);
-    timing.end();
-    return app;
-  }
-
-  private static DexApplication internalReadApp(
-      AndroidApp inputApp, InternalOptions options, Timing timing) throws IOException {
-    timing.begin("Internal Read App");
-    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
-    ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    assert !options.ignoreJavaLibraryOverride;
-    options.ignoreJavaLibraryOverride = true;
-    DexApplication app = applicationReader.read(executorService);
-    options.ignoreJavaLibraryOverride = false;
-    timing.end();
-    return app;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
index 473cfda..51262ef 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -34,12 +33,13 @@
   public HumanToMachinePrefixConverter(
       AppInfoWithClassHierarchy appInfo,
       MachineRewritingFlags.Builder builder,
-      HumanDesugaredLibrarySpecification humanSpec,
+      String synthesizedPrefix,
+      boolean libraryCompilation,
       HumanRewritingFlags rewritingFlags) {
     this.appInfo = appInfo;
     this.builder = builder;
-    this.synthesizedPrefix = humanSpec.getSynthesizedLibraryClassesPackagePrefix();
-    this.libraryCompilation = humanSpec.isLibraryCompilation();
+    this.synthesizedPrefix = synthesizedPrefix;
+    this.libraryCompilation = libraryCompilation;
     this.descriptorPrefix = convertRewritePrefix(rewritingFlags.getRewritePrefix());
     this.descriptorDontRewritePrefix = convertPrefixSet(rewritingFlags.getDontRewritePrefix());
     this.descriptorMaintainPrefix = convertPrefixSet(rewritingFlags.getMaintainPrefix());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 1097b06..eb333a7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -13,24 +13,26 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.CustomConversionDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineTopLevelFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecification;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
 import java.io.IOException;
-import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class HumanToMachineSpecificationConverter {
@@ -44,16 +46,42 @@
     this.timing = timing;
   }
 
-  public MachineDesugaredLibrarySpecification convertForTesting(
-      HumanDesugaredLibrarySpecification humanSpec,
-      Collection<Path> desugaredJDKLib,
-      Collection<Path> androidLib,
-      InternalOptions options)
+  public MultiAPILevelMachineDesugaredLibrarySpecification convertAllAPILevels(
+      MultiAPILevelHumanDesugaredLibrarySpecification humanSpec, DexApplication app)
       throws IOException {
-    DexApplication app =
-        AppForSpecConversion.readAppForTesting(
-            desugaredJDKLib, androidLib, options, humanSpec.isLibraryCompilation(), timing);
-    return convert(humanSpec, app);
+    timing.begin("Legacy to human all API convert");
+    reporter = app.options.reporter;
+    appInfo =
+        AppInfoWithClassHierarchy.createForDesugaring(
+            AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forNonSynthesizing()));
+
+    MachineTopLevelFlags machineTopLevelFlags = convertTopLevelFlags(humanSpec.getTopLevelFlags());
+    String synthesizedPrefix = machineTopLevelFlags.getSynthesizedLibraryClassesPackagePrefix();
+    Map<ApiLevelRange, MachineRewritingFlags> commonFlags =
+        convertRewritingFlagMap(humanSpec.getCommonFlags(), synthesizedPrefix, true);
+    Map<ApiLevelRange, MachineRewritingFlags> programFlags =
+        convertRewritingFlagMap(humanSpec.getProgramFlags(), synthesizedPrefix, false);
+    Map<ApiLevelRange, MachineRewritingFlags> libraryFlags =
+        convertRewritingFlagMap(humanSpec.getLibraryFlags(), synthesizedPrefix, true);
+
+    MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
+        new MultiAPILevelMachineDesugaredLibrarySpecification(
+            humanSpec.getOrigin(), machineTopLevelFlags, commonFlags, libraryFlags, programFlags);
+    timing.end();
+    return machineSpec;
+  }
+
+  private Map<ApiLevelRange, MachineRewritingFlags> convertRewritingFlagMap(
+      Map<ApiLevelRange, HumanRewritingFlags> libFlags,
+      String synthesizedPrefix,
+      boolean interpretAsLibraryCompilation) {
+    Map<ApiLevelRange, MachineRewritingFlags> map = new HashMap<>();
+    libFlags.forEach(
+        (range, flags) ->
+            map.put(
+                range,
+                convertRewritingFlags(flags, synthesizedPrefix, interpretAsLibraryCompilation)));
+    return map;
   }
 
   public MachineDesugaredLibrarySpecification convert(
@@ -67,7 +95,11 @@
         app,
         humanSpec.isLibraryCompilation(),
         humanSpec.getTopLevelFlags().getRequiredCompilationAPILevel());
-    MachineRewritingFlags machineRewritingFlags = convertRewritingFlags(humanSpec);
+    MachineRewritingFlags machineRewritingFlags =
+        convertRewritingFlags(
+            humanSpec.getRewritingFlags(),
+            humanSpec.getSynthesizedLibraryClassesPackagePrefix(),
+            humanSpec.isLibraryCompilation());
     MachineTopLevelFlags topLevelFlags = convertTopLevelFlags(humanSpec.getTopLevelFlags());
     timing.end();
     return new MachineDesugaredLibrarySpecification(
@@ -85,9 +117,8 @@
   }
 
   private MachineRewritingFlags convertRewritingFlags(
-      HumanDesugaredLibrarySpecification humanSpec) {
+      HumanRewritingFlags rewritingFlags, String synthesizedPrefix, boolean libraryCompilation) {
     timing.begin("convert rewriting flags");
-    HumanRewritingFlags rewritingFlags = humanSpec.getRewritingFlags();
     MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
     DesugaredLibraryAmender.run(
         rewritingFlags.getAmendLibraryMethod(),
@@ -102,7 +133,8 @@
         .convertRetargetFlags(rewritingFlags, builder, this::warnMissingReferences);
     new HumanToMachineEmulatedInterfaceConverter(appInfo)
         .convertEmulatedInterfaces(rewritingFlags, appInfo, builder, this::warnMissingReferences);
-    new HumanToMachinePrefixConverter(appInfo, builder, humanSpec, rewritingFlags)
+    new HumanToMachinePrefixConverter(
+            appInfo, builder, synthesizedPrefix, libraryCompilation, rewritingFlags)
         .convertPrefixFlags(rewritingFlags, this::warnMissingDexString);
     new HumanToMachineWrapperConverter(appInfo)
         .convertWrappers(rewritingFlags, builder, this::warnMissingReferences);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
index 9ac3369..c0b124b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
 
-import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.StringResource;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -23,15 +21,12 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.Timing;
@@ -39,9 +34,7 @@
 import com.google.common.collect.ImmutableMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import java.io.IOException;
-import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -60,32 +53,11 @@
     this.timing = timing;
   }
 
-  public void convertAllAPILevels(
-      StringResource inputSpecification,
-      Collection<Path> desugaredJDKLib,
-      Collection<Path> androidLib,
-      StringConsumer output)
-      throws IOException {
-    InternalOptions options = new InternalOptions();
-    MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec =
-        new MultiAPILevelLegacyDesugaredLibrarySpecificationParser(
-                options.dexItemFactory(), options.reporter)
-            .parseMultiLevelConfiguration(inputSpecification);
-    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
-        convertAllAPILevels(legacySpec, desugaredJDKLib, androidLib, options);
-    MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(humanSpec, output);
-  }
-
   public MultiAPILevelHumanDesugaredLibrarySpecification convertAllAPILevels(
-      MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec,
-      Collection<Path> desugaredJDKLib,
-      Collection<Path> androidLib,
-      InternalOptions options)
+      MultiAPILevelLegacyDesugaredLibrarySpecification legacySpec, DexApplication app)
       throws IOException {
     timing.begin("Legacy to human all API convert");
     Origin origin = legacySpec.getOrigin();
-    DexApplication app =
-        AppForSpecConversion.readAppForTesting(desugaredJDKLib, androidLib, options, true, timing);
 
     HumanTopLevelFlags humanTopLevelFlags = convertTopLevelFlags(legacySpec.getTopLevelFlags());
     Map<ApiLevelRange, HumanRewritingFlags> commonFlags =
@@ -102,23 +74,11 @@
         new MultiAPILevelHumanDesugaredLibrarySpecification(
             origin, humanTopLevelFlags, commonFlags, libraryFlags, programFlags);
     MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.deduplicateFlags(
-        humanSpec, options.reporter);
+        humanSpec, app.options.reporter);
     timing.end();
     return humanSpec;
   }
 
-  public HumanDesugaredLibrarySpecification convertForTesting(
-      LegacyDesugaredLibrarySpecification legacySpec,
-      Collection<Path> desugaredJDKLib,
-      Collection<Path> androidLib,
-      InternalOptions options)
-      throws IOException {
-    DexApplication app =
-        AppForSpecConversion.readAppForTesting(
-            desugaredJDKLib, androidLib, options, legacySpec.isLibraryCompilation(), timing);
-    return convert(legacySpec, app);
-  }
-
   public HumanDesugaredLibrarySpecification convert(
       LegacyDesugaredLibrarySpecification legacySpec, DexApplication app) throws IOException {
     timing.begin("Legacy to Human convert");
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 92467a3..16821510 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
@@ -49,6 +49,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
@@ -656,6 +657,27 @@
           && !emulatedInterfaceInfo.contains(resolvedHolder.type)) {
         return true;
       }
+      // If the method overrides an abstract method which does not match the criteria, then
+      // forwarding methods are required so the invokes on the top level holder can resolve at
+      // runtime. See b/202188674.
+      if (overridesAbstractNonLibraryInterfaceMethod(
+          clazz, signature.get(), emulatedInterfaceInfo)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean overridesAbstractNonLibraryInterfaceMethod(
+      DexClass clazz, DexMethod dexMethod, EmulatedInterfaceInfo emulatedInterfaceInfo) {
+    List<Entry<DexClass, DexEncodedMethod>> abstractInterfaceMethods =
+        appView.appInfoForDesugaring().getAbstractInterfaceMethods(clazz, dexMethod);
+    for (Entry<DexClass, DexEncodedMethod> classAndMethod : abstractInterfaceMethods) {
+      DexClass foundMethodClass = classAndMethod.getKey();
+      if (!foundMethodClass.isLibraryClass()
+          && !emulatedInterfaceInfo.contains(foundMethodClass.type)) {
+        return true;
+      }
     }
     return false;
   }
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 38afb5d..85802c1 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
@@ -95,6 +95,7 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
+import com.android.tools.r8.ir.code.UnusedArgument;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
@@ -3738,6 +3739,14 @@
     iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, empty)));
   }
 
+  public static void replaceUnusedArgumentTrivialPhis(UnusedArgument unusedArgument) {
+    replaceTrivialPhis(unusedArgument.outValue());
+    for (Phi phiUser : unusedArgument.outValue().uniquePhiUsers()) {
+      phiUser.removeTrivialPhi();
+    }
+    assert !unusedArgument.outValue().hasPhiUsers();
+  }
+
   public static void ensureDirectStringNewToInit(IRCode code, DexItemFactory dexItemFactory) {
     for (Instruction instruction : code.instructions()) {
       if (instruction.isInvokeDirect()) {
@@ -3747,7 +3756,7 @@
             && method.holder == dexItemFactory.stringType
             && invoke.getReceiver().isPhi()) {
           NewInstance newInstance = findNewInstance(invoke.getReceiver().asPhi());
-          replaceTrivialNewInstancePhis(newInstance.outValue());
+          replaceTrivialPhis(newInstance.outValue());
           if (invoke.getReceiver().isPhi()) {
             throw new CompilationError(
                 "Failed to remove trivial phis between new-instance and <init>");
@@ -3810,18 +3819,15 @@
     }
   }
 
-  // If an <init> call takes place on a phi the code must contain an irreducible loop between the
-  // new-instance and the <init>. Assuming the code is verifiable, new-instance must flow to a
-  // unique <init>. Here we compute the set of strongly connected phis making use of the
-  // new-instance value and replace all trivial ones by the new-instance value.
+  // We compute the set of strongly connected phis making use of the out value and replace all
+  // trivial ones by the out value.
   // This is a simplified variant of the removeRedundantPhis algorithm in Section 3.2 of:
   // http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
-  private static void replaceTrivialNewInstancePhis(Value newInstanceValue) {
-    List<Set<Value>> components =
-        new SCC<Value>(Value::uniquePhiUsers).computeSCC(newInstanceValue);
+  private static void replaceTrivialPhis(Value outValue) {
+    List<Set<Value>> components = new SCC<Value>(Value::uniquePhiUsers).computeSCC(outValue);
     for (int i = components.size() - 1; i >= 0; i--) {
       Set<Value> component = components.get(i);
-      if (component.size() == 1 && component.iterator().next() == newInstanceValue) {
+      if (component.size() == 1 && component.iterator().next() == outValue) {
         continue;
       }
       Set<Phi> trivialPhis = Sets.newIdentityHashSet();
@@ -3829,7 +3835,7 @@
         boolean isTrivial = true;
         Phi p = value.asPhi();
         for (Value op : p.getOperands()) {
-          if (op != newInstanceValue && !component.contains(op)) {
+          if (op != outValue && !component.contains(op)) {
             isTrivial = false;
             break;
           }
@@ -3842,7 +3848,7 @@
         for (Value op : trivialPhi.getOperands()) {
           op.removePhiUser(trivialPhi);
         }
-        trivialPhi.replaceUsers(newInstanceValue);
+        trivialPhi.replaceUsers(outValue);
         trivialPhi.getBlock().removePhi(trivialPhi);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 4867829..525041d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -837,7 +837,7 @@
           reportFailure(
               enumClass,
               new MissingInstanceFieldValueForEnumInstanceReason(
-                  staticField.getReference(), factory.enumMembers.ordinalField));
+                  factory.enumMembers.ordinalField, staticField.getReference()));
           return null;
         }
         int ordinal = optionalOrdinal.getAsInt();
@@ -926,7 +926,14 @@
       Int2ReferenceMap<ObjectState> ordinalToObjectState) {
     DexEncodedField encodedInstanceField =
         appView.appInfo().resolveFieldOn(enumClass, instanceField).getResolvedField();
-    assert encodedInstanceField != null;
+    if (encodedInstanceField == null) {
+      // This seems to be happening in b/238911016 but we do not have a reproduction.
+      // If this assert fails, it would be nice to understand what is going on and potentially
+      // fix the code below to do something more appropriate than bailing out.
+      assert false;
+      reportFailure(enumClass, new MissingInstanceFieldValueForEnumInstanceReason(instanceField));
+      return EnumInstanceFieldUnknownData.getInstance();
+    }
     boolean canBeOrdinal = instanceField.type.isIntType();
     ImmutableInt2ReferenceSortedMap.Builder<AbstractValue> data =
         ImmutableInt2ReferenceSortedMap.builder();
@@ -935,7 +942,7 @@
       AbstractValue fieldValue = state.getAbstractFieldValue(encodedInstanceField);
       if (!fieldValue.isSingleValue()) {
         reportFailure(
-            enumClass, new MissingInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
+            enumClass, new MissingInstanceFieldValueForEnumInstanceReason(instanceField, ordinal));
         return EnumInstanceFieldUnknownData.getInstance();
       }
       if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index bac0275..63fc7fd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -120,14 +120,20 @@
     private final int ordinal;
     private final DexField instanceField;
 
+    public MissingInstanceFieldValueForEnumInstanceReason(DexField instanceField) {
+      this.enumField = null;
+      this.ordinal = -1;
+      this.instanceField = instanceField;
+    }
+
     public MissingInstanceFieldValueForEnumInstanceReason(
-        DexField enumField, DexField instanceField) {
+        DexField instanceField, DexField enumField) {
       this.enumField = enumField;
       this.ordinal = -1;
       this.instanceField = instanceField;
     }
 
-    public MissingInstanceFieldValueForEnumInstanceReason(int ordinal, DexField instanceField) {
+    public MissingInstanceFieldValueForEnumInstanceReason(DexField instanceField, int ordinal) {
       this.enumField = null;
       this.ordinal = ordinal;
       this.instanceField = instanceField;
@@ -147,6 +153,11 @@
             + instanceField.toSourceString()
             + ")";
       }
+      if (ordinal == -1) {
+        return "MissingInstanceFieldValueForEnumInstance(Cannot resolve instance field="
+            + instanceField.toSourceString()
+            + ")";
+      }
       assert ordinal >= 0;
       return "MissingInstanceFieldValueForEnumInstance(ordinal="
           + ordinal
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 aae3f42..f9266b0 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -35,6 +35,8 @@
  */
 public class ClassNamingForNameMapper implements ClassNaming {
 
+  private static final List<MappingInformation> EMPTY_MAPPING_INFORMATION = Collections.emptyList();
+
   public static class Builder extends ClassNaming.Builder {
 
     private final String originalName;
@@ -43,7 +45,7 @@
     private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap();
     private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
     private final Map<String, List<MemberNaming>> mappedFieldNamingsByName = Maps.newHashMap();
-    private final List<MappingInformation> additionalMappingInfo = new ArrayList<>();
+    private List<MappingInformation> additionalMappingInfo = EMPTY_MAPPING_INFORMATION;
     private final BiConsumer<String, String> originalSourceFileConsumer;
 
     private Builder(
@@ -107,6 +109,9 @@
     @Override
     public void addMappingInformation(
         MappingInformation info, Consumer<MappingInformation> onProhibitedAddition) {
+      if (additionalMappingInfo == EMPTY_MAPPING_INFORMATION) {
+        additionalMappingInfo = new ArrayList<>();
+      }
       for (MappingInformation existing : additionalMappingInfo) {
         if (!existing.allowOther(info)) {
           onProhibitedAddition.accept(existing);
@@ -497,7 +502,7 @@
      */
     private final int sequenceNumber = getNextSequenceNumber();
 
-    private List<MappingInformation> additionalMappingInfo = new ArrayList<>();
+    private List<MappingInformation> additionalMappingInfo = EMPTY_MAPPING_INFORMATION;
 
     private MappedRange(
         Range minifiedRange, MethodSignature signature, Range originalRange, String renamedName) {
@@ -509,6 +514,9 @@
 
     public void addMappingInformation(
         MappingInformation info, Consumer<MappingInformation> onProhibitedAddition) {
+      if (additionalMappingInfo == EMPTY_MAPPING_INFORMATION) {
+        additionalMappingInfo = new ArrayList<>();
+      }
       for (MappingInformation existing : additionalMappingInfo) {
         if (!existing.allowOther(info)) {
           onProhibitedAddition.accept(existing);
@@ -630,8 +638,7 @@
     }
 
     public List<MappingInformation> getAdditionalMappingInfo() {
-      return additionalMappingInfo;
+      return Collections.unmodifiableList(additionalMappingInfo);
     }
   }
 }
-
diff --git a/src/main/java/com/android/tools/r8/naming/MapVersion.java b/src/main/java/com/android/tools/r8/naming/MapVersion.java
index ae93504..8d8c9e0 100644
--- a/src/main/java/com/android/tools/r8/naming/MapVersion.java
+++ b/src/main/java/com/android/tools/r8/naming/MapVersion.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.utils.structural.Ordered;
 
 @Keep
@@ -35,6 +36,10 @@
     return null;
   }
 
+  public MapVersionMappingInformation toMapVersionMappingInformation() {
+    return new MapVersionMappingInformation(this, this.getName());
+  }
+
   public boolean isUnknown() {
     return this == MAP_VERSION_UNKNOWN;
   }
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 bdd2b9b..6bde14bf 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
-import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IdentifierUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -23,6 +23,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Parses a Proguard mapping file and produces mappings from obfuscated class names to the original
@@ -425,8 +426,37 @@
     }
   }
 
-  private TextPosition getPosition() {
-    return new TextPosition(0, lineNo, 1);
+  private Position getPosition() {
+    return new LinePosition(lineNo);
+  }
+
+  private static final class LinePosition implements Position {
+    private final int lineNo;
+
+    LinePosition(int lineNo) {
+      this.lineNo = lineNo;
+    }
+
+    @Override
+    public String getDescription() {
+      return "line " + lineNo;
+    }
+
+    @Override
+    public int hashCode() {
+      return lineNo;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o == this) {
+        return true;
+      }
+      if (o instanceof LinePosition) {
+        return lineNo == ((LinePosition) o).lineNo;
+      }
+      return false;
+    }
   }
 
   // Parsing of components
@@ -460,17 +490,42 @@
     }
   }
 
+  // Small direct-mapped cache for computing String.substring.
+  //
+  // Due to inlining, the same method names and parameter types will occur repeatedly on multiple
+  // lines.  String.substring ends up allocating a lot of garbage, so we use this cache to find
+  // String objects without having to allocate memory.
+  //
+  // "Direct-mapped" is inspired from computer architecture, where having a lookup policy in
+  // which entries can only ever map to one cache line is often faster than a fancy LRU cache.
+  private static final int SUBSTRING_CACHE_SIZE = 64;
+  private final String[] substringCache = new String[SUBSTRING_CACHE_SIZE];
   // Cache for canonicalizing strings.
   // This saves 10% of heap space for large programs.
-  final HashMap<String, String> cache = new HashMap<>();
+  private final HashMap<String, String> identifierCache = new HashMap<>();
+
+  // Cache for canonicalizing signatures.
+  //
+  // Due to inlining, the same MethodSignature will come up many times in a ProguardMap.
+  // This happens to help a bit for FieldSignature too, so lump those in.
+  private final HashMap<Signature, Signature> signatureCache = new HashMap<>();
 
   private String substring(int start) {
-    String result = line.substring(start, lineOffset);
-    if (cache.containsKey(result)) {
-      return cache.get(result);
+    int cacheIdx;
+    {
+      // Check if there was a recent String accessed which matches the substring.
+      int len = lineOffset - start;
+      cacheIdx = len % SUBSTRING_CACHE_SIZE;
+      String candidate = substringCache[cacheIdx];
+      if (candidate != null
+          && candidate.length() == len
+          && line.regionMatches(start, candidate, 0, len)) {
+        return candidate;
+      }
     }
-    cache.put(result, result);
-    return result;
+
+    String result = line.substring(start, lineOffset);
+    return substringCache[cacheIdx] = identifierCache.computeIfAbsent(result, Function.identity());
   }
 
   private String parseMethodName() {
@@ -510,7 +565,7 @@
       skipWhitespace();
       String[] arguments;
       if (peekChar(0) == ')') {
-        arguments = new String[0];
+        arguments = StringUtils.EMPTY_ARRAY;
       } else {
         List<String> items = new ArrayList<>();
         items.add(parseType(true));
@@ -528,7 +583,7 @@
     } else {
       signature = new FieldSignature(name, type);
     }
-    return signature;
+    return signatureCache.computeIfAbsent(signature, Function.identity());
   }
 
   private void skipArrow() {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 63c2dfc..af66d9e 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -121,7 +120,7 @@
     if (mapVersion.isGreaterThan(MapVersion.MAP_VERSION_NONE)) {
       builder
           .append("# ")
-          .append(new MapVersionMappingInformation(mapVersion, mapVersion.getName()).serialize())
+          .append(mapVersion.toMapVersionMappingInformation().serialize())
           .append("\n");
     }
     builder.append("# " + MARKER_KEY_PG_MAP_ID + ": " + id.getId() + "\n");
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
index 707023d..add5db3 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
@@ -6,12 +6,12 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.retrace.internal.MappingSupplierInternal;
+import java.util.Set;
 
 @Keep
-public abstract class MappingSupplier<T extends MappingSupplier<T>>
-    extends MappingSupplierInternal {
+public abstract class MappingSupplier<T extends MappingSupplier<T>> {
 
   /***
    * Register an allowed mapping lookup to allow for prefetching of resources.
@@ -22,4 +22,9 @@
       DiagnosticsHandler diagnosticsHandler, ClassReference classReference);
 
   public abstract void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler);
+
+  public abstract Set<MapVersionMappingInformation> getMapVersions(
+      DiagnosticsHandler diagnosticsHandler);
+
+  public abstract Retracer createRetracer(DiagnosticsHandler diagnosticsHandler);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java
index 74cd98f..28b95a0 100644
--- a/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java
@@ -19,5 +19,7 @@
       extends MappingSupplierBuilder<ProguardMappingSupplier, Builder> {
 
     public abstract Builder setProguardMapProducer(ProguardMapProducer proguardMapProducer);
+
+    public abstract Builder setLoadAllDefinitions(boolean loadAllDefinitions);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 29b68ab..a3cf547 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.internal.ResultWithContextImpl;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
 import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
 import com.android.tools.r8.utils.Box;
@@ -132,7 +131,7 @@
     return builder;
   }
 
-  private static MappingSupplier getMappingSupplier(
+  private static MappingSupplier<?> getMappingSupplier(
       String mappingPath, DiagnosticsHandler diagnosticsHandler) {
     Path path = Paths.get(mappingPath);
     if (!Files.exists(path)) {
@@ -145,6 +144,7 @@
     return ProguardMappingSupplier.builder()
         .setProguardMapProducer(ProguardMapProducer.fromPath(Paths.get(mappingPath)))
         .setAllowExperimental(allowExperimentalMapVersion)
+        .setLoadAllDefinitions(false)
         .build();
   }
 
@@ -318,28 +318,6 @@
       DiagnosticsHandler diagnosticsHandler = options.getDiagnosticsHandler();
       StackTraceRegularExpressionParser stackTraceLineParser =
           new StackTraceRegularExpressionParser(options.getRegularExpression());
-      timing.begin("Read proguard map");
-      RetracerImpl retracer =
-          RetracerImpl.builder()
-              .setMappingSupplier(mappingSupplier)
-              .setDiagnosticsHandler(diagnosticsHandler)
-              .build();
-      retracer
-          .getMapVersions()
-          .forEach(
-              mapVersionInfo -> {
-                if (mapVersionInfo.getMapVersion().isUnknown()) {
-                  diagnosticsHandler.warning(
-                      RetraceUnknownMapVersionDiagnostic.create(mapVersionInfo.getValue()));
-                }
-              });
-      StringRetrace stringRetracer =
-          new StringRetrace(
-              stackTraceLineParser,
-              StackTraceElementProxyRetracer.createDefault(retracer),
-              diagnosticsHandler,
-              options.isVerbose());
-      timing.end();
       StackTraceSupplier stackTraceSupplier = command.getStacktraceSupplier();
       int lineNumber = 0;
       RetraceStackTraceContext context = RetraceStackTraceContext.empty();
@@ -374,6 +352,15 @@
                     mappingSupplier, proxy.getFieldOrReturnType(), diagnosticsHandler);
               }
             });
+        timing.begin("Read proguard map");
+        StringRetrace stringRetracer =
+            new StringRetrace(
+                stackTraceLineParser,
+                StackTraceElementProxyRetracer.createDefault(
+                    mappingSupplier.createRetracer(diagnosticsHandler)),
+                diagnosticsHandler,
+                options.isVerbose());
+        timing.end();
         timing.begin("Retracing");
         ResultWithContext<String> result = stringRetracer.retraceParsed(parsedStackTrace, context);
         timing.end();
@@ -387,6 +374,15 @@
       if (command.printTimes()) {
         timing.report();
       }
+      mappingSupplier
+          .getMapVersions(diagnosticsHandler)
+          .forEach(
+              mapVersionInfo -> {
+                if (mapVersionInfo.getMapVersion().isUnknown()) {
+                  diagnosticsHandler.warning(
+                      RetraceUnknownMapVersionDiagnostic.create(mapVersionInfo.getValue()));
+                }
+              });
     } catch (InvalidMappingFileException e) {
       command.getOptions().getDiagnosticsHandler().error(new ExceptionDiagnostic(e));
       throw e;
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 650883b..633b143 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
 import java.util.OptionalInt;
 
 /** This is the main api interface for retrace. */
@@ -73,18 +72,12 @@
   static Retracer createDefault(
       ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
     try {
-      ProguardMappingSupplier mappingSupplier =
-          ProguardMappingSupplier.builder().setProguardMapProducer(proguardMapProducer).build();
-      return Retracer.builder()
-          .setMappingSupplier(mappingSupplier)
-          .setDiagnosticsHandler(diagnosticsHandler)
-          .build();
+      return ProguardMappingSupplier.builder()
+          .setProguardMapProducer(proguardMapProducer)
+          .build()
+          .createRetracer(diagnosticsHandler);
     } catch (Exception e) {
       throw new InvalidMappingFileException(e);
     }
   }
-
-  static RetracerBuilder builder() {
-    return RetracerImpl.builder();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java b/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java
deleted file mode 100644
index 21eb8e0..0000000
--- a/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java
+++ /dev/null
@@ -1,18 +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.retrace;
-
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Keep;
-
-@Keep
-public interface RetracerBuilder {
-
-  RetracerBuilder setMappingSupplier(MappingSupplier mappingSupplier);
-
-  RetracerBuilder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler);
-
-  Retracer build();
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index 164a208..5a7a797 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.retrace.internal.ResultWithContextImpl;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -42,10 +41,7 @@
    */
   public static StringRetrace create(RetraceOptions command) {
     return create(
-        RetracerImpl.builder()
-            .setMappingSupplier(command.getMappingSupplier())
-            .setDiagnosticsHandler(command.getDiagnosticsHandler())
-            .build(),
+        command.getMappingSupplier().createRetracer(command.getDiagnosticsHandler()),
         command.getDiagnosticsHandler(),
         command.getRegularExpression(),
         command.isVerbose());
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternal.java
index c47c467..b2e8508 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternal.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternal.java
@@ -4,17 +4,15 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import java.util.Set;
 
 public abstract class MappingSupplierInternal {
 
-  abstract ClassNamingForNameMapper getClassNaming(
-      DiagnosticsHandler diagnosticsHandler, String typeName);
+  abstract ClassNamingForNameMapper getClassNaming(String typeName);
 
-  abstract String getSourceFileForClass(DiagnosticsHandler diagnosticsHandler, String typeName);
+  abstract String getSourceFileForClass(String typeName);
 
-  abstract Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler);
+  abstract Set<MapVersionMappingInformation> getMapVersions();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternalImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternalImpl.java
new file mode 100644
index 0000000..277ad7b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingSupplierInternalImpl.java
@@ -0,0 +1,38 @@
+// 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.retrace.internal;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import java.util.Set;
+
+public class MappingSupplierInternalImpl extends MappingSupplierInternal {
+
+  private final ClassNameMapper classNameMapper;
+
+  MappingSupplierInternalImpl(ClassNameMapper classNameMapper) {
+    this.classNameMapper = classNameMapper;
+  }
+
+  @Override
+  ClassNamingForNameMapper getClassNaming(String typeName) {
+    return classNameMapper.getClassNaming(typeName);
+  }
+
+  @Override
+  String getSourceFileForClass(String typeName) {
+    return classNameMapper.getSourceFile(typeName);
+  }
+
+  @Override
+  Set<MapVersionMappingInformation> getMapVersions() {
+    return classNameMapper.getMapVersions();
+  }
+
+  public static MappingSupplierInternal createInternal(ClassNameMapper classNameMapper) {
+    return new MappingSupplierInternalImpl(classNameMapper);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
index dfddd37..d37d45c 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
@@ -8,12 +8,10 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.LineReader;
 import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.InvalidMappingFileException;
 import com.android.tools.r8.retrace.MappingPartitionFromKeySupplier;
 import com.android.tools.r8.retrace.PartitionMappingSupplier;
@@ -72,25 +70,33 @@
   }
 
   @Override
-  Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler) {
+  public PartitionMappingSupplier registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
+    registerKeyUse(getMetadata(diagnosticsHandler).getKey(classReference));
+    return this;
+  }
+
+  private void registerKeyUse(String key) {
+    if (!builtKeys.contains(key) && pendingKeys.add(key)) {
+      registerPartitionCallback.register(key);
+    }
+  }
+
+  @Override
+  public void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler) {
+    String errorMessage = "Cannot verify map file hash for partitions";
+    diagnosticsHandler.error(new StringDiagnostic(errorMessage));
+    throw new RuntimeException(errorMessage);
+  }
+
+  @Override
+  public Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler) {
     return Collections.singleton(
-        new MapVersionMappingInformation(getMetadata(diagnosticsHandler).getMapVersion(), ""));
+        getMetadata(diagnosticsHandler).getMapVersion().toMapVersionMappingInformation());
   }
 
   @Override
-  ClassNamingForNameMapper getClassNaming(DiagnosticsHandler diagnosticsHandler, String typeName) {
-    registerClassUse(diagnosticsHandler, Reference.classFromTypeName(typeName));
-    return getClassNameMapper(diagnosticsHandler).getClassNaming(typeName);
-  }
-
-  @Override
-  String getSourceFileForClass(DiagnosticsHandler diagnosticsHandler, String typeName) {
-    // Getting source file should not trigger new fetches of partitions so we are not calling
-    // register here.
-    return getClassNameMapper(diagnosticsHandler).getSourceFile(typeName);
-  }
-
-  private ClassNameMapper getClassNameMapper(DiagnosticsHandler diagnosticsHandler) {
+  public RetracerImpl createRetracer(DiagnosticsHandler diagnosticsHandler) {
     MappingPartitionMetadataInternal metadata = getMetadata(diagnosticsHandler);
     if (!pendingKeys.isEmpty()) {
       prepare.prepare();
@@ -113,26 +119,7 @@
     if (classNameMapper == null) {
       classNameMapper = ClassNameMapper.builder().build();
     }
-    return classNameMapper;
-  }
-
-  @Override
-  public PartitionMappingSupplier registerClassUse(
-      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
-    registerKeyUse(getMetadata(diagnosticsHandler).getKey(classReference));
-    return this;
-  }
-
-  private void registerKeyUse(String key) {
-    if (!builtKeys.contains(key) && pendingKeys.add(key)) {
-      registerPartitionCallback.register(key);
-    }
-  }
-
-  @Override
-  public void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler) {
-    String errorMessage = "Cannot verify map file hash for partitions";
-    diagnosticsHandler.error(new StringDiagnostic(errorMessage));
-    throw new RuntimeException(errorMessage);
+    return RetracerImpl.createInternal(
+        MappingSupplierInternalImpl.createInternal(classNameMapper), diagnosticsHandler);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierBuilderImpl.java
index 8cea599..ca8f08b 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierBuilderImpl.java
@@ -11,6 +11,7 @@
 
   private ProguardMapProducer proguardMapProducer;
   private boolean allowExperimental = false;
+  private boolean loadAllDefinitions = true;
 
   @Override
   public ProguardMappingSupplier.Builder self() {
@@ -31,7 +32,14 @@
   }
 
   @Override
+  public ProguardMappingSupplier.Builder setLoadAllDefinitions(boolean loadAllDefinitions) {
+    this.loadAllDefinitions = loadAllDefinitions;
+    return self();
+  }
+
+  @Override
   public ProguardMappingSupplier build() {
-    return new ProguardMappingSupplierImpl(proguardMapProducer, allowExperimental);
+    return new ProguardMappingSupplierImpl(
+        proguardMapProducer, allowExperimental, loadAllDefinitions);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
index 632ca66..14f41ed 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingSupplierImpl.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.LineReader;
 import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker;
@@ -35,86 +34,32 @@
  */
 public class ProguardMappingSupplierImpl extends ProguardMappingSupplier {
 
-  private final ProguardMapProducer proguardMapProducer;
+  private ProguardMapProducer proguardMapProducer;
   private final boolean allowExperimental;
+  private final boolean loadAllDefinitions;
 
   private ClassNameMapper classNameMapper;
   private final Set<String> pendingClassMappings = new HashSet<>();
-  private final Set<String> builtClassMappings;
+  private final Set<String> builtClassMappings = new HashSet<>();
 
   public ProguardMappingSupplierImpl(ClassNameMapper classNameMapper) {
     this.classNameMapper = classNameMapper;
     this.proguardMapProducer = null;
     this.allowExperimental = true;
-    builtClassMappings = null;
+    this.loadAllDefinitions = true;
   }
 
-  ProguardMappingSupplierImpl(ProguardMapProducer proguardMapProducer, boolean allowExperimental) {
+  ProguardMappingSupplierImpl(
+      ProguardMapProducer proguardMapProducer,
+      boolean allowExperimental,
+      boolean loadAllDefinitions) {
     this.proguardMapProducer = proguardMapProducer;
     this.allowExperimental = allowExperimental;
-    builtClassMappings = proguardMapProducer.isFileBacked() ? new HashSet<>() : null;
+    this.loadAllDefinitions = loadAllDefinitions;
   }
 
   private boolean hasClassMappingFor(String typeName) {
-    return builtClassMappings == null || builtClassMappings.contains(typeName);
-  }
-
-  @Override
-  Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler) {
-    return getClassNameMapper(diagnosticsHandler).getMapVersions();
-  }
-
-  @Override
-  ClassNamingForNameMapper getClassNaming(DiagnosticsHandler diagnosticsHandler, String typeName) {
-    if (!hasClassMappingFor(typeName)) {
-      pendingClassMappings.add(typeName);
-    }
-    return getClassNameMapper(diagnosticsHandler).getClassNaming(typeName);
-  }
-
-  @Override
-  String getSourceFileForClass(DiagnosticsHandler diagnosticsHandler, String typeName) {
-    return getClassNameMapper(diagnosticsHandler).getSourceFile(typeName);
-  }
-
-  private ClassNameMapper getClassNameMapper(DiagnosticsHandler diagnosticsHandler) {
-    if (classNameMapper != null && pendingClassMappings.isEmpty()) {
-      return classNameMapper;
-    }
-    if (classNameMapper != null && !proguardMapProducer.isFileBacked()) {
-      throw new RuntimeException("Cannot re-open a proguard map producer that is not file backed");
-    }
-    try {
-      Predicate<String> buildForClass =
-          builtClassMappings == null ? null : pendingClassMappings::contains;
-      boolean readPreambleAndSourceFile = classNameMapper == null;
-      LineReader reader =
-          proguardMapProducer.isFileBacked()
-              ? new ProguardMapReaderWithFilteringMappedBuffer(
-                  proguardMapProducer.getPath(), buildForClass, readPreambleAndSourceFile)
-              : new ProguardMapReaderWithFilteringInputBuffer(
-                  proguardMapProducer.get(), buildForClass, readPreambleAndSourceFile);
-      ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromLineReaderWithFiltering(
-              reader, getMapVersion(), diagnosticsHandler, true, allowExperimental);
-      this.classNameMapper = classNameMapper.combine(this.classNameMapper);
-      if (builtClassMappings != null) {
-        builtClassMappings.addAll(pendingClassMappings);
-      }
-      pendingClassMappings.clear();
-    } catch (Exception e) {
-      throw new InvalidMappingFileException(e);
-    }
-    return classNameMapper;
-  }
-
-  private MapVersion getMapVersion() {
-    if (classNameMapper == null) {
-      return MapVersion.MAP_VERSION_NONE;
-    } else {
-      MapVersionMappingInformation mapVersion = classNameMapper.getFirstMapVersionInformation();
-      return mapVersion == null ? MapVersion.MAP_VERSION_UNKNOWN : mapVersion.getMapVersion();
-    }
+    return loadAllDefinitions || builtClassMappings.contains(typeName);
   }
 
   @Override
@@ -144,4 +89,57 @@
       throw new RuntimeException(e);
     }
   }
+
+  @Override
+  public Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler) {
+    if (classNameMapper == null) {
+      createRetracer(diagnosticsHandler);
+    }
+    assert classNameMapper != null;
+    return classNameMapper.getMapVersions();
+  }
+
+  @Override
+  public RetracerImpl createRetracer(DiagnosticsHandler diagnosticsHandler) {
+    if (proguardMapProducer == null) {
+      assert classNameMapper != null;
+      return RetracerImpl.createInternal(
+          MappingSupplierInternalImpl.createInternal(classNameMapper), diagnosticsHandler);
+    }
+    if (classNameMapper == null || !pendingClassMappings.isEmpty()) {
+      try {
+        Predicate<String> buildForClass =
+            loadAllDefinitions ? null : pendingClassMappings::contains;
+        boolean readPreambleAndSourceFile = classNameMapper == null;
+        LineReader reader =
+            proguardMapProducer.isFileBacked()
+                ? new ProguardMapReaderWithFilteringMappedBuffer(
+                    proguardMapProducer.getPath(), buildForClass, readPreambleAndSourceFile)
+                : new ProguardMapReaderWithFilteringInputBuffer(
+                    proguardMapProducer.get(), buildForClass, readPreambleAndSourceFile);
+        classNameMapper =
+            ClassNameMapper.mapperFromLineReaderWithFiltering(
+                    reader, getMapVersion(), diagnosticsHandler, true, allowExperimental)
+                .combine(classNameMapper);
+        builtClassMappings.addAll(pendingClassMappings);
+        pendingClassMappings.clear();
+      } catch (Exception e) {
+        throw new InvalidMappingFileException(e);
+      }
+    }
+    if (loadAllDefinitions) {
+      proguardMapProducer = null;
+    }
+    return RetracerImpl.createInternal(
+        MappingSupplierInternalImpl.createInternal(classNameMapper), diagnosticsHandler);
+  }
+
+  private MapVersion getMapVersion() {
+    if (classNameMapper == null) {
+      return MapVersion.MAP_VERSION_NONE;
+    } else {
+      MapVersionMappingInformation mapVersion = classNameMapper.getFirstMapVersionInformation();
+      return mapVersion == null ? MapVersion.MAP_VERSION_UNKNOWN : mapVersion.getMapVersion();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index daec745..5b93dec 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -5,18 +5,14 @@
 package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.MappingSupplier;
 import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.Retracer;
-import com.android.tools.r8.retrace.RetracerBuilder;
 import java.util.OptionalInt;
-import java.util.Set;
 
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
 public class RetracerImpl implements Retracer {
@@ -70,9 +66,7 @@
   @Override
   public RetraceClassResultImpl retraceClass(ClassReference classReference) {
     return RetraceClassResultImpl.create(
-        classReference,
-        classNameMapperSupplier.getClassNaming(diagnosticsHandler, classReference.getTypeName()),
-        this);
+        classReference, classNameMapperSupplier.getClassNaming(classReference.getTypeName()), this);
   }
 
   @Override
@@ -85,41 +79,12 @@
     return retraceClass(exception).lookupThrownException(RetraceStackTraceContext.empty());
   }
 
-  public Set<MapVersionMappingInformation> getMapVersions() {
-    return classNameMapperSupplier.getMapVersions(diagnosticsHandler);
-  }
-
   public String getSourceFile(ClassReference classReference) {
-    return classNameMapperSupplier.getSourceFileForClass(
-        diagnosticsHandler, classReference.getTypeName());
+    return classNameMapperSupplier.getSourceFileForClass(classReference.getTypeName());
   }
 
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder implements RetracerBuilder {
-
-    private MappingSupplier mappingSupplier;
-    private DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
-
-    private Builder() {}
-
-    @Override
-    public Builder setMappingSupplier(MappingSupplier mappingSupplier) {
-      this.mappingSupplier = mappingSupplier;
-      return this;
-    }
-
-    @Override
-    public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
-      this.diagnosticsHandler = diagnosticsHandler;
-      return this;
-    }
-
-    @Override
-    public RetracerImpl build() {
-      return new RetracerImpl(mappingSupplier, diagnosticsHandler);
-    }
+  public static RetracerImpl createInternal(
+      MappingSupplierInternal classNameMapperSupplier, DiagnosticsHandler diagnosticsHandler) {
+    return new RetracerImpl(classNameMapperSupplier, diagnosticsHandler);
   }
 }
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 1df8107..4d9598e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1346,24 +1346,17 @@
 
   void traceMethodHandle(
       DexMethodHandle methodHandle, MethodHandleUse use, ProgramMethod currentMethod) {
-    // If a method handle is not an argument to a lambda metafactory it could flow to a
-    // MethodHandle.invokeExact invocation. For that to work, the receiver type cannot have
-    // changed and therefore we cannot perform member rebinding. For these handles, we maintain
-    // the receiver for the method handle. Therefore, we have to make sure that the receiver
-    // stays in the output (and is not class merged). To ensure that we treat the receiver
-    // as instantiated.
     if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
-      DexType type = methodHandle.asMethod().holder;
-      DexProgramClass clazz = getProgramClassOrNull(type, currentMethod);
-      if (clazz != null) {
-        KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
-        if (clazz.isAnnotation()) {
-          markTypeAsLive(clazz, graphReporter.registerClass(clazz, reason));
-        } else if (clazz.isInterface()) {
-          markInterfaceAsInstantiated(clazz, graphReporter.registerInterface(clazz, reason));
-        } else {
-          workList.enqueueMarkInstantiatedAction(
-              clazz, null, InstantiationReason.REFERENCED_IN_METHOD_HANDLE, reason);
+      KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
+      MethodResolutionResult result =
+          resolveMethod(methodHandle.asMethod(), currentMethod, reason, methodHandle.isInterface);
+      if (result.isSingleResolution()) {
+        DexClassAndMethod target = result.asSingleResolution().getResolutionPair();
+        if (target.isProgramMethod()) {
+          // If the method handle is targeting a program method then the structure of the method
+          // must remain, so that invoke/invokeExact dispatches will continue to match.
+          applyMinimumKeepInfoWhenLiveOrTargeted(
+              target.asProgramMethod(), KeepMethodInfo.newEmptyJoiner().disallowOptimization());
         }
       }
     }
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 78547de..0c68c28 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.debuginfo.DebugRepresentation;
+import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -170,6 +171,8 @@
   // TODO(zerny): Make this private-final once we have full program-consumer support.
   public ProgramConsumer programConsumer = null;
 
+  public ProgramClassConflictResolver programClassConflictResolver = null;
+
   private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
 
   public DataResourceConsumer dataResourceConsumer;
@@ -372,6 +375,9 @@
   public boolean enableRedundantConstNumberOptimization = false;
   public boolean enableLoopUnrolling = true;
 
+  // TODO(b/237567012): Remove when resolved.
+  public boolean enableCheckAllInstructionsDuringStackMapVerification = false;
+
   public String synthesizedClassPrefix = "";
 
   // Number of threads to use while processing the dex files.
@@ -397,14 +403,15 @@
   // Boolean value indicating that byte code pass through may be enabled.
   public boolean enableCfByteCodePassThrough = false;
 
-  // TODO(b/238175192): remove again when resolved
-  public boolean enableUnrepresentableInDexInstructionRemoval = false;
-
   // Flag to control the representation of stateless lambdas.
   // See b/222081665 for context.
   public boolean createSingletonsForStatelessLambdas =
       System.getProperty("com.android.tools.r8.createSingletonsForStatelessLambdas") != null;
 
+  // Flag to allow record annotations in DEX. See b/231930852 for context.
+  public boolean emitRecordAnnotationsInDex =
+      System.getProperty("com.android.tools.r8.emitRecordAnnotationsInDex") != null;
+
   // Flag to allow nest annotations in DEX. See b/231930852 for context.
   public boolean emitNestAnnotationsInDex =
       System.getProperty("com.android.tools.r8.emitNestAnnotationsInDex") != null;
@@ -2148,7 +2155,7 @@
   }
 
   public boolean canUseRecords() {
-    return hasFeaturePresentFrom(null);
+    return hasFeaturePresentFrom(null) || emitRecordAnnotationsInDex;
   }
 
   public boolean canUseSealedClasses() {
diff --git a/src/main/keep.txt b/src/main/keep.txt
index deb8e6d..ced5b4a 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -24,7 +24,6 @@
 -keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature, RuntimeInvisibleAnnotations
 
 -keepparameternames
--keeppackagenames com.android.tools.r8
 -repackageclasses com.android.tools.r8.internal
 
 # Compatibility command line program used by the Android Platform build.
diff --git a/src/test/examplesAndroidO/invokepolymorphic/InvokePolymorphic.java b/src/test/examplesAndroidO/invokepolymorphic/InvokePolymorphic.java
deleted file mode 100644
index 90cb064..0000000
--- a/src/test/examplesAndroidO/invokepolymorphic/InvokePolymorphic.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package invokepolymorphic;
-
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-class Data {
-}
-
-public class InvokePolymorphic {
-
-  public String buildString(Integer i1, int i2, String s) {
-    return (i1 == null ? "N" : "!N") + "-" + i2 + "-" + s;
-  }
-
-  public void testInvokePolymorphic() {
-    MethodType mt = MethodType.methodType(String.class, Integer.class, int.class, String.class);
-    MethodHandles.Lookup lk = MethodHandles.lookup();
-
-    try {
-      MethodHandle mh = lk.findVirtual(getClass(), "buildString", mt);
-      System.out.println(mh.invoke(this, null, 1, "string"));
-    } catch (Throwable t) {
-      t.printStackTrace();
-    }
-  }
-
-  public String buildString(
-      byte b, char c, short s, float f, double d, long l, Integer i1, int i2, String str) {
-    return b + "-" + c + "-" + s + "-" + f + "-" + d + "-" + l + "-" + (i1 == null ? "N" : "!N")
-        + "-" + i2 + "-" + str;
-  }
-
-  public void testInvokePolymorphicRange() {
-    MethodType mt = MethodType.methodType(String.class, byte.class, char.class, short.class,
-        float.class, double.class, long.class, Integer.class, int.class, String.class);
-    MethodHandles.Lookup lk = MethodHandles.lookup();
-
-    try {
-      MethodHandle mh = lk.findVirtual(getClass(), "buildString", mt);
-      System.out.println(
-          mh.invoke(this, (byte) 2, 'a', (short) 0xFFFF, 1.1f, 2.24d, 12345678L, null,
-              1, "string"));
-    } catch (Throwable t) {
-      t.printStackTrace();
-    }
-  }
-
-  public static void testWithAllTypes(
-      boolean z, char a, short b, int c, long d, float e, double f, String g, Object h) {
-    System.out.println(z);
-    System.out.println(a);
-    System.out.println(b);
-    System.out.println(c);
-    System.out.println(d);
-    System.out.println(e);
-    System.out.println(f);
-    System.out.println(g);
-    System.out.println(h);
-  }
-
-  public void testInvokePolymorphicWithAllTypes() {
-    try {
-      MethodHandle mth =
-          MethodHandles.lookup()
-              .findStatic(
-                  InvokePolymorphic.class,
-                  "testWithAllTypes",
-                  MethodType.methodType(
-                      void.class, boolean.class, char.class, short.class, int.class, long.class,
-                      float.class, double.class, String.class, Object.class));
-      mth.invokeExact(false,'h', (short) 56, 72, Integer.MAX_VALUE + 42l,
-          0.56f, 100.0d, "hello", (Object) "goodbye");
-    } catch (Throwable t) {
-      t.printStackTrace();
-    }
-  }
-
-  public MethodHandle testInvokePolymorphicWithConstructor() {
-    MethodHandle mh = null;
-    MethodType mt = MethodType.methodType(void.class);
-    MethodHandles.Lookup lk = MethodHandles.lookup();
-
-    try {
-      mh = lk.findConstructor(Data.class, mt);
-      System.out.println(mh.invoke().getClass() == Data.class);
-    } catch (Throwable t) {
-      t.printStackTrace();
-    }
-
-    return mh;
-  }
-
-  public static void main(String[] args) {
-    InvokePolymorphic invokePolymorphic = new InvokePolymorphic();
-    invokePolymorphic.testInvokePolymorphic();
-    invokePolymorphic.testInvokePolymorphicRange();
-    invokePolymorphic.testInvokePolymorphicWithAllTypes();
-    invokePolymorphic.testInvokePolymorphicWithConstructor();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/CommandTestBase.java b/src/test/java/com/android/tools/r8/CommandTestBase.java
index 1c0c588..ed755f9 100644
--- a/src/test/java/com/android/tools/r8/CommandTestBase.java
+++ b/src/test/java/com/android/tools/r8/CommandTestBase.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.AppForSpecConversion;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -313,13 +312,7 @@
     InternalOptions options = command.getInternalOptions();
     LibraryDesugaringSpecification spec = LibraryDesugaringSpecification.JDK11;
     options.loadMachineDesugaredLibrarySpecification(
-        Timing.empty(),
-        AppForSpecConversion.readAppForTesting(
-            libraryCompilation ? spec.getDesugarJdkLibs() : null,
-            spec.getLibraryFiles(),
-            options,
-            libraryCompilation,
-            Timing.empty()));
+        Timing.empty(), spec.getAppForTesting(options, libraryCompilation));
     return options;
   }
 
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 7fe1ae0..c2c74a4 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -213,9 +213,7 @@
     abstract void build(Path inputFile, Path out, OutputMode mode) throws Throwable;
   }
 
-  private static List<String> minSdkErrorExpected =
-      ImmutableList.of(
-          "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
+  private static List<String> minSdkErrorExpected = ImmutableList.of();
 
   private static Map<DexVm.Version, List<String>> failsOn;
 
@@ -229,7 +227,6 @@
                 "paramnames",
                 "repeat_annotations_new_api",
                 // Dex version not supported
-                "invokepolymorphic",
                 "invokecustom",
                 "invokecustom2",
                 "DefaultMethodInAndroidJar25",
@@ -244,7 +241,6 @@
                 "paramnames",
                 "repeat_annotations_new_api",
                 // Dex version not supported
-                "invokepolymorphic",
                 "invokecustom",
                 "invokecustom2",
                 "DefaultMethodInAndroidJar25",
@@ -259,7 +255,6 @@
                 "paramnames",
                 "repeat_annotations_new_api",
                 // Dex version not supported
-                "invokepolymorphic",
                 "invokecustom",
                 "invokecustom2",
                 "DefaultMethodInAndroidJar25",
@@ -274,7 +269,6 @@
                 "paramnames",
                 "repeat_annotations_new_api",
                 // Dex version not supported
-                "invokepolymorphic",
                 "invokecustom",
                 "invokecustom2",
                 "DefaultMethodInAndroidJar25",
@@ -288,7 +282,6 @@
                 // API not supported
                 "paramnames",
                 // Dex version not supported
-                "invokepolymorphic",
                 "invokecustom",
                 "invokecustom2",
                 "testMissingInterfaceDesugared2AndroidO",
@@ -370,30 +363,6 @@
   }
 
   @Test
-  public void invokeCustomErrorDueToMinSdk() throws Throwable {
-    test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
-        .withMinApiLevel(AndroidApiLevel.N_MR1)
-        .withKeepAll()
-        .run();
-  }
-
-  @Test
-  public void invokePolymorphic() throws Throwable {
-    test("invokepolymorphic", "invokepolymorphic", "InvokePolymorphic")
-        .withMinApiLevel(AndroidApiLevel.O)
-        .withKeepAll()
-        .run();
-  }
-
-  @Test
-  public void invokePolymorphicErrorDueToMinSdk() throws Throwable {
-    test("invokepolymorphic-error-due-to-min-sdk", "invokepolymorphic", "InvokePolymorphic")
-        .withMinApiLevel(AndroidApiLevel.N_MR1)
-        .withKeepAll()
-        .run();
-  }
-
-  @Test
   public void lambdaDesugaring() throws Throwable {
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index caed567..c8b6cc4 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -176,8 +176,7 @@
   }
 
   private static List<String> minSdkErrorExpected =
-      ImmutableList.of(
-          "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
+      ImmutableList.of("invokecustom-error-due-to-min-sdk");
 
   private static Map<DexVm.Version, List<String>> failsOn =
       ImmutableMap.<DexVm.Version, List<String>>builder()
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 2b29085..4150236 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -251,12 +251,6 @@
     return self();
   }
 
-  public T mapUnsupportedFeaturesToWarnings() {
-    return setDiagnosticsLevelModifier(
-        (level, diagnostic) ->
-            diagnostic instanceof UnsupportedFeatureDiagnostic ? DiagnosticsLevel.WARNING : level);
-  }
-
   public T allowStdoutMessages() {
     // Default ignored.
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 9072aa3..e1de2d3 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -105,6 +105,10 @@
     return addKeepRules(Arrays.asList(rules));
   }
 
+  public T addDontObfuscate() {
+    return addKeepRules("-dontobfuscate");
+  }
+
   public T addDontObfuscate(Class<?> clazz) {
     return addKeepRules(
         "-keep,allowaccessmodification,allowannotationremoval,allowoptimization,allowshrinking"
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 69f2167..88b929a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -168,9 +168,11 @@
       Paths.get(LIBS_DIR, "r8_no_manifest_without_deps.jar");
   public static final Path R8_WITH_RELOCATED_DEPS_JAR =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps.jar");
-  public static final Path R8_WITH_DEPS_11_JAR = Paths.get(LIBS_DIR, "r8_with_deps_11.jar");
   public static final Path R8_WITH_RELOCATED_DEPS_11_JAR =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps_11.jar");
+  public static final Path R8_WITH_DEPS_17_JAR = Paths.get(LIBS_DIR, "r8_with_deps_17.jar");
+  public static final Path R8_WITH_RELOCATED_DEPS_17_JAR =
+      Paths.get(LIBS_DIR, "r8_with_relocated_deps_17.jar");
   public static final Path R8LIB_JAR = Paths.get(LIBS_DIR, "r8lib.jar");
   public static final Path R8LIB_MAP = Paths.get(LIBS_DIR, "r8lib.jar.map");
   public static final Path R8LIB_EXCLUDE_DEPS_JAR = Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar");
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
new file mode 100644
index 0000000..4012ad7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
@@ -0,0 +1,243 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+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 ApiModelMockExceptionTest extends TestBase {
+
+  private final AndroidApiLevel mockSuperExceptionLevel = AndroidApiLevel.M;
+  private final AndroidApiLevel mockSubExceptionLevel = AndroidApiLevel.P;
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private boolean isGreaterOrEqualToSuperMockLevel() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(mockSuperExceptionLevel);
+  }
+
+  private boolean isGreaterOrEqualToSubMockLevel() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(mockSubExceptionLevel);
+  }
+
+  private void setupTestCompileBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder
+        .addProgramClasses(Main.class)
+        .addLibraryClasses(LibrarySuperException.class, LibrarySubException.class, Thrower.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(setMockApiLevelForClass(LibrarySuperException.class, mockSuperExceptionLevel))
+        .apply(setMockApiLevelForClass(LibrarySubException.class, mockSubExceptionLevel));
+  }
+
+  private void setupTestRuntimeBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder.setMinApi(parameters.getApiLevel()).addAndroidBuildVersion();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    setupTestCompileBuilder(testBuilder);
+    setupTestRuntimeBuilder(testBuilder);
+  }
+
+  private boolean addSuperToBootClasspath() {
+    return parameters.isCfRuntime()
+        || parameters
+            .getRuntime()
+            .maxSupportedApiLevel()
+            .isGreaterThanOrEqualTo(mockSuperExceptionLevel);
+  }
+
+  private boolean addSubToBootClasspath() {
+    return parameters.isCfRuntime()
+        || parameters
+            .getRuntime()
+            .maxSupportedApiLevel()
+            .isGreaterThanOrEqualTo(mockSubExceptionLevel);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .addBootClasspathClasses(Thrower.class)
+        .applyIf(
+            addSuperToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySuperException.class))
+        .applyIf(addSubToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySubException.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .addBootClasspathClasses(Thrower.class)
+        .applyIf(
+            addSuperToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySuperException.class))
+        .applyIf(addSubToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySubException.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8MergeIndexed() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testD8Merge(OutputMode.DexIndexed);
+  }
+
+  @Test
+  public void testD8MergeFilePerClass() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testD8Merge(OutputMode.DexFilePerClass);
+  }
+
+  @Test
+  public void testD8MergeFilePerClassFile() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testD8Merge(OutputMode.DexFilePerClassFile);
+  }
+
+  public void testD8Merge(OutputMode outputMode) throws Exception {
+    GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+    Path incrementalOut =
+        testForD8()
+            .debug()
+            .setOutputMode(outputMode)
+            .setIntermediate(true)
+            .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals))
+            .apply(this::setupTestCompileBuilder)
+            .compile()
+            .writeToZip();
+
+    if (isGreaterOrEqualToSubMockLevel()) {
+      assertFalse(globals.hasGlobals());
+    } else if (outputMode == OutputMode.DexIndexed) {
+      assertTrue(globals.hasGlobals());
+      assertTrue(globals.isSingleGlobal());
+    } else {
+      assertTrue(globals.hasGlobals());
+      // The Main class reference the mock and should have globals.
+      assertNotNull(globals.getProvider(Reference.classFromClass(Main.class)));
+    }
+
+    testForD8()
+        .debug()
+        .addProgramFiles(incrementalOut)
+        .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals.getProviders()))
+        .apply(this::setupTestRuntimeBuilder)
+        .compile()
+        .addBootClasspathClasses(Thrower.class)
+        .applyIf(
+            addSuperToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySuperException.class))
+        .applyIf(addSubToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySubException.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .compile()
+        .addBootClasspathClasses(Thrower.class)
+        .applyIf(
+            addSuperToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySuperException.class))
+        .applyIf(addSubToBootClasspath(), b -> b.addBootClasspathClasses(LibrarySubException.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    verifyThat(inspector, parameters, LibrarySuperException.class)
+        .stubbedUntil(mockSuperExceptionLevel);
+    verifyThat(inspector, parameters, LibrarySubException.class)
+        .stubbedUntil(mockSubExceptionLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (isGreaterOrEqualToSubMockLevel()) {
+      runResult.assertSuccessWithOutputLines("Caught LibrarySubException");
+    } else if (isGreaterOrEqualToSuperMockLevel()) {
+      runResult.assertSuccessWithOutputLines("Caught LibrarySuperException");
+    } else {
+      runResult.assertSuccessWithOutputLines("Caught Exception");
+    }
+  }
+
+  // Only present from api level M.
+  public static class LibrarySuperException extends Exception {}
+
+  // Only present from api level P.
+  public static class LibrarySubException extends LibrarySuperException {}
+
+  // Only present from api level P.
+  public static class Thrower {
+
+    public static void test(int apiVersion) throws Exception {
+      if (apiVersion >= 28) {
+        throw new LibrarySubException();
+      } else if (apiVersion >= 23) {
+        throw new LibrarySuperException();
+      } else {
+        throw new Exception();
+      }
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      try {
+        Thrower.test(AndroidBuildVersion.VERSION);
+      } catch (LibrarySubException e) {
+        System.out.println("Caught LibrarySubException");
+      } catch (LibrarySuperException e) {
+        System.out.println("Caught LibrarySuperException");
+      } catch (Exception e) {
+        System.out.println("Caught Exception");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodTest.java
new file mode 100644
index 0000000..10fc6da
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeToPackagePrivateMethodTest.java
@@ -0,0 +1,120 @@
+// 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.bridgeremoval.hoisting;
+
+import com.android.tools.r8.NeverClassInline;
+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.google.common.collect.ImmutableList;
+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 BridgeToPackagePrivateMethodTest extends TestBase {
+
+  private static final List<String> EXPECTED_OUTPUT = ImmutableList.of("A", "B");
+  private static final String TRANSFORMED_A_DESCRIPTOR = "LA;";
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
+                .transform(),
+            transformer(A.class).setClassDescriptor(TRANSFORMED_A_DESCRIPTOR).transform(),
+            transformer(B.class)
+                .setSuper(TRANSFORMED_A_DESCRIPTOR)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
+                .setBridge(B.class.getDeclaredMethod("bridge"))
+                .transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
+                .transform(),
+            transformer(A.class).setClassDescriptor(TRANSFORMED_A_DESCRIPTOR).transform(),
+            transformer(B.class)
+                .setSuper(TRANSFORMED_A_DESCRIPTOR)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(A.class), TRANSFORMED_A_DESCRIPTOR)
+                .setBridge(B.class.getDeclaredMethod("bridge"))
+                .transform())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/233866639): Should succeed with "A", "B".
+        .applyIf(
+            parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik(),
+            runResult -> runResult.assertSuccessWithOutputLines(EXPECTED_OUTPUT),
+            runResult -> runResult.assertSuccessWithOutputLines("A", "A"));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().callM();
+      new B().bridge();
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class /*otherpackage.*/ A {
+
+    @NeverInline
+    void m() {
+      System.out.println("A");
+    }
+
+    @NeverInline
+    public void callM() {
+      m();
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    @NeverInline
+    void m() {
+      System.out.println("B");
+    }
+
+    // Not eligible for bridge hoisting, as the bridge will then dispatch to A.m instead of B.m.
+    @NeverInline
+    public /*bridge*/ void bridge() {
+      this.m();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest.java b/src/test/java/com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest.java
new file mode 100644
index 0000000..32ce06a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest.java
@@ -0,0 +1,996 @@
+// 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.cf;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+/** This is a regresson test for b/237567012 */
+public class CfDebugLocalStackMapVerificationTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/237567012): We should not fail compilation.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClassFileData(CfDebugLocalStackMapVerificationTest$MainDump.dump())
+                .addKeepMainRule(Main.class)
+                .setMode(CompilationMode.DEBUG)
+                .addDontWarn("*")
+                // TODO(b/237567012): Remove option when resolved.
+                .addOptionsModification(
+                    options -> options.enableCheckAllInstructionsDuringStackMapVerification = true)
+                .compile()
+                .run(parameters.getRuntime(), Main.class));
+  }
+
+  public static class Main {
+
+    // InvokeSuspend is taken from an input program.jar and copied verbatim in the dump below.
+    public Object invokeSuspend(Object o) {
+      return o;
+    }
+
+    public static void main(String[] args) {
+      new Main().invokeSuspend(null);
+    }
+  }
+
+  public static class CfDebugLocalStackMapVerificationTest$MainDump implements Opcodes {
+
+    public static byte[] dump() throws Exception {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      MethodVisitor methodVisitor;
+      AnnotationVisitor annotationVisitor0;
+
+      classWriter.visit(
+          V1_8,
+          ACC_FINAL | ACC_SUPER,
+          binaryName(Main.class),
+          null,
+          "kotlin/coroutines/jvm/internal/SuspendLambda",
+          new String[] {"kotlin/jvm/functions/Function2"});
+
+      {
+        fieldVisitor = classWriter.visitField(0, "L$1", "Ljava/lang/Object;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor = classWriter.visitField(0, "L$2", "Ljava/lang/Object;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor = classWriter.visitField(0, "label", "I", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_PRIVATE | ACC_SYNTHETIC, "L$0", "Ljava/lang/Object;", null, null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        fieldVisitor =
+            classWriter.visitField(
+                ACC_FINAL | ACC_SYNTHETIC,
+                "this$0",
+                "Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;",
+                null,
+                null);
+        fieldVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                0,
+                "<init>",
+                "(Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;Lkotlin/coroutines/Continuation;)V",
+                "(Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;Lkotlin/coroutines/Continuation<-Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1;>;)V",
+                null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "this$0",
+            "Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitInsn(ICONST_2);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "kotlin/coroutines/jvm/internal/SuspendLambda",
+            "<init>",
+            "(ILkotlin/coroutines/Continuation;)V",
+            false);
+        methodVisitor.visitInsn(RETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1;",
+            null,
+            label0,
+            label1,
+            0);
+        methodVisitor.visitLocalVariable(
+            "$receiver",
+            "Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;",
+            null,
+            label0,
+            label1,
+            1);
+        methodVisitor.visitLocalVariable(
+            "$completion", "Lkotlin/coroutines/Continuation;", null, label0, label1, 2);
+        methodVisitor.visitMaxs(3, 3);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_FINAL,
+                "invokeSuspend",
+                "(Ljava/lang/Object;)Ljava/lang/Object;",
+                null,
+                null);
+        {
+          annotationVisitor0 =
+              methodVisitor.visitAnnotation("Lorg/jetbrains/annotations/Nullable;", false);
+          annotationVisitor0.visitEnd();
+        }
+        methodVisitor.visitAnnotableParameterCount(1, false);
+        {
+          annotationVisitor0 =
+              methodVisitor.visitParameterAnnotation(
+                  0, "Lorg/jetbrains/annotations/NotNull;", false);
+          annotationVisitor0.visitEnd();
+        }
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        Label label1 = new Label();
+        Label label2 = new Label();
+        methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Throwable");
+        Label label3 = new Label();
+        Label label4 = new Label();
+        methodVisitor.visitTryCatchBlock(label3, label4, label2, "java/lang/Throwable");
+        Label label5 = new Label();
+        Label label6 = new Label();
+        methodVisitor.visitTryCatchBlock(label5, label6, label2, "java/lang/Throwable");
+        Label label7 = new Label();
+        methodVisitor.visitTryCatchBlock(label0, label1, label7, null);
+        methodVisitor.visitTryCatchBlock(label3, label4, label7, null);
+        methodVisitor.visitTryCatchBlock(label5, label6, label7, null);
+        methodVisitor.visitTryCatchBlock(label2, label7, label7, null);
+        Label label8 = new Label();
+        methodVisitor.visitTryCatchBlock(label7, label8, label7, null);
+        Label label9 = new Label();
+        Label label10 = new Label();
+        methodVisitor.visitTryCatchBlock(
+            label9, label1, label10, "kotlinx/coroutines/channels/ClosedReceiveChannelException");
+        methodVisitor.visitTryCatchBlock(
+            label3, label4, label10, "kotlinx/coroutines/channels/ClosedReceiveChannelException");
+        methodVisitor.visitTryCatchBlock(
+            label5, label10, label10, "kotlinx/coroutines/channels/ClosedReceiveChannelException");
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "kotlin/coroutines/intrinsics/IntrinsicsKt",
+            "getCOROUTINE_SUSPENDED",
+            "()Ljava/lang/Object;",
+            false);
+        Label label11 = new Label();
+        methodVisitor.visitLabel(label11);
+        methodVisitor.visitLineNumber(60, label11);
+        methodVisitor.visitVarInsn(ASTORE, 10);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "label",
+            "I");
+        Label label12 = new Label();
+        Label label13 = new Label();
+        Label label14 = new Label();
+        Label label15 = new Label();
+        methodVisitor.visitTableSwitchInsn(0, 2, label15, new Label[] {label12, label13, label14});
+        methodVisitor.visitLabel(label12);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "kotlin/ResultKt", "throwOnFailure", "(Ljava/lang/Object;)V", false);
+        Label label16 = new Label();
+        methodVisitor.visitLabel(label16);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$0",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(CHECKCAST, "kotlinx/coroutines/CoroutineScope");
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        methodVisitor.visitLabel(label9);
+        methodVisitor.visitLineNumber(61, label9);
+        methodVisitor.visitInsn(NOP);
+        Label label17 = new Label();
+        methodVisitor.visitLabel(label17);
+        methodVisitor.visitLineNumber(62, label17);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "this$0",
+            "Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;");
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+            "access$getQueue$p",
+            "(Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;)Lkotlinx/coroutines/channels/Channel;",
+            false);
+        methodVisitor.visitTypeInsn(CHECKCAST, "kotlinx/coroutines/channels/ReceiveChannel");
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "this$0",
+            "Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;");
+        methodVisitor.visitVarInsn(ASTORE, 4);
+        Label label18 = new Label();
+        methodVisitor.visitLabel(label18);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 5);
+        Label label19 = new Label();
+        methodVisitor.visitLabel(label19);
+        methodVisitor.visitLineNumber(146, label19);
+        methodVisitor.visitInsn(NOP);
+        Label label20 = new Label();
+        methodVisitor.visitLabel(label20);
+        methodVisitor.visitLineNumber(149, label20);
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitVarInsn(ASTORE, 6);
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(150, label0);
+        methodVisitor.visitInsn(NOP);
+        Label label21 = new Label();
+        methodVisitor.visitLabel(label21);
+        methodVisitor.visitLineNumber(151, label21);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 7);
+        Label label22 = new Label();
+        methodVisitor.visitLabel(label22);
+        methodVisitor.visitLineNumber(63, label22);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "kotlinx/coroutines/channels/ReceiveChannel",
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+              Opcodes.INTEGER,
+              Opcodes.NULL,
+              Opcodes.INTEGER,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "kotlinx/coroutines/CoroutineScopeKt",
+            "isActive",
+            "(Lkotlinx/coroutines/CoroutineScope;)Z",
+            false);
+        Label label23 = new Label();
+        methodVisitor.visitJumpInsn(IFEQ, label23);
+        Label label24 = new Label();
+        methodVisitor.visitLabel(label24);
+        methodVisitor.visitLineNumber(64, label24);
+        methodVisitor.visitVarInsn(ALOAD, 4);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+            "access$getQueue$p",
+            "(Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;)Lkotlinx/coroutines/channels/Channel;",
+            false);
+        methodVisitor.visitMethodInsn(
+            INVOKEINTERFACE,
+            "kotlinx/coroutines/channels/Channel",
+            "tryReceive-PtdJZtk",
+            "()Ljava/lang/Object;",
+            true);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "kotlinx/coroutines/channels/ChannelResult",
+            "getOrNull-impl",
+            "(Ljava/lang/Object;)Ljava/lang/Object;",
+            false);
+        methodVisitor.visitTypeInsn(CHECKCAST, "java/nio/ByteBuffer");
+        methodVisitor.visitVarInsn(ASTORE, 8);
+        Label label25 = new Label();
+        methodVisitor.visitLabel(label25);
+        methodVisitor.visitLineNumber(65, label25);
+        methodVisitor.visitVarInsn(ALOAD, 8);
+        Label label26 = new Label();
+        methodVisitor.visitJumpInsn(IFNONNULL, label26);
+        Label label27 = new Label();
+        methodVisitor.visitLabel(label27);
+        methodVisitor.visitLineNumber(66, label27);
+        methodVisitor.visitVarInsn(ALOAD, 4);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+            "subscription",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(CHECKCAST, "java/util/concurrent/Flow$Subscription");
+        methodVisitor.visitInsn(DUP);
+        Label label28 = new Label();
+        methodVisitor.visitJumpInsn(IFNULL, label28);
+        methodVisitor.visitInsn(LCONST_1);
+        methodVisitor.visitMethodInsn(
+            INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
+        Label label29 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label29);
+        methodVisitor.visitLabel(label28);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "kotlinx/coroutines/channels/ReceiveChannel",
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+              Opcodes.INTEGER,
+              Opcodes.NULL,
+              Opcodes.INTEGER,
+              "java/nio/ByteBuffer",
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            1,
+            new Object[] {"java/util/concurrent/Flow$Subscription"});
+        methodVisitor.visitInsn(POP);
+        methodVisitor.visitLabel(label29);
+        methodVisitor.visitLineNumber(67, label29);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitVarInsn(ALOAD, 4);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+            "access$getQueue$p",
+            "(Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;)Lkotlinx/coroutines/channels/Channel;",
+            false);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$0",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$1",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 4);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$2",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitInsn(ICONST_1);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "label",
+            "I");
+        methodVisitor.visitMethodInsn(
+            INVOKEINTERFACE,
+            "kotlinx/coroutines/channels/Channel",
+            "receive",
+            "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;",
+            true);
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitVarInsn(ALOAD, 10);
+        Label label30 = new Label();
+        methodVisitor.visitJumpInsn(IF_ACMPNE, label30);
+        Label label31 = new Label();
+        methodVisitor.visitLabel(label31);
+        methodVisitor.visitLineNumber(60, label31);
+        methodVisitor.visitVarInsn(ALOAD, 10);
+        methodVisitor.visitInsn(ARETURN);
+        methodVisitor.visitLabel(label13);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 5);
+        Label label32 = new Label();
+        methodVisitor.visitLabel(label32);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 7);
+        Label label33 = new Label();
+        methodVisitor.visitLabel(label33);
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitVarInsn(ASTORE, 6);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$2",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(
+            CHECKCAST,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber");
+        methodVisitor.visitVarInsn(ASTORE, 4);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$1",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(CHECKCAST, "kotlinx/coroutines/channels/ReceiveChannel");
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label34 = new Label();
+        methodVisitor.visitLabel(label34);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$0",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(CHECKCAST, "kotlinx/coroutines/CoroutineScope");
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitInsn(NOP);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "kotlin/ResultKt", "throwOnFailure", "(Ljava/lang/Object;)V", false);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitLabel(label30);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "kotlinx/coroutines/channels/ReceiveChannel",
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+              Opcodes.INTEGER,
+              Opcodes.NULL,
+              Opcodes.INTEGER,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            1,
+            new Object[] {"java/lang/Object"});
+        methodVisitor.visitTypeInsn(CHECKCAST, "java/nio/ByteBuffer");
+        methodVisitor.visitVarInsn(ASTORE, 8);
+        methodVisitor.visitLabel(label26);
+        methodVisitor.visitLineNumber(70, label26);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "kotlinx/coroutines/channels/ReceiveChannel",
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+              Opcodes.INTEGER,
+              Opcodes.NULL,
+              Opcodes.INTEGER,
+              "java/nio/ByteBuffer",
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitVarInsn(ALOAD, 4);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+            "access$getResponseChannel$p",
+            "(Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber;)Lio/ktor/utils/io/ByteChannel;",
+            false);
+        methodVisitor.visitVarInsn(ALOAD, 8);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 2);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$0",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$1",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitVarInsn(ALOAD, 4);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$2",
+            "Ljava/lang/Object;");
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitInsn(ICONST_2);
+        methodVisitor.visitFieldInsn(
+            PUTFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "label",
+            "I");
+        methodVisitor.visitMethodInsn(
+            INVOKEINTERFACE,
+            "io/ktor/utils/io/ByteChannel",
+            "writeFully",
+            "(Ljava/nio/ByteBuffer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;",
+            true);
+        methodVisitor.visitLabel(label4);
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitVarInsn(ALOAD, 10);
+        Label label35 = new Label();
+        methodVisitor.visitJumpInsn(IF_ACMPNE, label35);
+        Label label36 = new Label();
+        methodVisitor.visitLabel(label36);
+        methodVisitor.visitLineNumber(60, label36);
+        methodVisitor.visitVarInsn(ALOAD, 10);
+        methodVisitor.visitInsn(ARETURN);
+        methodVisitor.visitLabel(label14);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 5);
+        Label label37 = new Label();
+        methodVisitor.visitLabel(label37);
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitVarInsn(ISTORE, 7);
+        Label label38 = new Label();
+        methodVisitor.visitLabel(label38);
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitVarInsn(ASTORE, 6);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$2",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(
+            CHECKCAST,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber");
+        methodVisitor.visitVarInsn(ASTORE, 4);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$1",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(CHECKCAST, "kotlinx/coroutines/channels/ReceiveChannel");
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        Label label39 = new Label();
+        methodVisitor.visitLabel(label39);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitFieldInsn(
+            GETFIELD,
+            "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+            "L$0",
+            "Ljava/lang/Object;");
+        methodVisitor.visitTypeInsn(CHECKCAST, "kotlinx/coroutines/CoroutineScope");
+        methodVisitor.visitVarInsn(ASTORE, 2);
+        methodVisitor.visitLabel(label5);
+        methodVisitor.visitInsn(NOP);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "kotlin/ResultKt", "throwOnFailure", "(Ljava/lang/Object;)V", false);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitLabel(label35);
+        methodVisitor.visitLineNumber(70, label35);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "kotlinx/coroutines/channels/ReceiveChannel",
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+              Opcodes.INTEGER,
+              Opcodes.NULL,
+              Opcodes.INTEGER,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            1,
+            new Object[] {"java/lang/Object"});
+        methodVisitor.visitInsn(POP);
+        methodVisitor.visitJumpInsn(GOTO, label22);
+        methodVisitor.visitLabel(label23);
+        methodVisitor.visitLineNumber(72, label23);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitInsn(NOP);
+        Label label40 = new Label();
+        methodVisitor.visitLabel(label40);
+        methodVisitor.visitFieldInsn(GETSTATIC, "kotlin/Unit", "INSTANCE", "Lkotlin/Unit;");
+        methodVisitor.visitVarInsn(ASTORE, 9);
+        methodVisitor.visitLabel(label6);
+        methodVisitor.visitLineNumber(156, label6);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitVarInsn(ALOAD, 6);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "kotlinx/coroutines/channels/ChannelsKt",
+            "cancelConsumed",
+            "(Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Throwable;)V",
+            false);
+        Label label41 = new Label();
+        methodVisitor.visitLabel(label41);
+        methodVisitor.visitLineNumber(151, label41);
+        Label label42 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label42);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLineNumber(152, label2);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "kotlinx/coroutines/channels/ReceiveChannel",
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+              Opcodes.INTEGER,
+              Opcodes.NULL,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            1,
+            new Object[] {"java/lang/Throwable"});
+        methodVisitor.visitVarInsn(ASTORE, 9);
+        Label label43 = new Label();
+        methodVisitor.visitLabel(label43);
+        methodVisitor.visitLineNumber(153, label43);
+        methodVisitor.visitVarInsn(ALOAD, 9);
+        methodVisitor.visitVarInsn(ASTORE, 6);
+        Label label44 = new Label();
+        methodVisitor.visitLabel(label44);
+        methodVisitor.visitLineNumber(154, label44);
+        methodVisitor.visitVarInsn(ALOAD, 9);
+        methodVisitor.visitInsn(ATHROW);
+        methodVisitor.visitLabel(label7);
+        methodVisitor.visitLineNumber(155, label7);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "kotlinx/coroutines/channels/ReceiveChannel",
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber",
+              Opcodes.INTEGER,
+              "java/lang/Throwable",
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            1,
+            new Object[] {"java/lang/Throwable"});
+        methodVisitor.visitVarInsn(ASTORE, 9);
+        methodVisitor.visitLabel(label8);
+        methodVisitor.visitLineNumber(156, label8);
+        methodVisitor.visitVarInsn(ALOAD, 3);
+        methodVisitor.visitVarInsn(ALOAD, 6);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "kotlinx/coroutines/channels/ChannelsKt",
+            "cancelConsumed",
+            "(Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Throwable;)V",
+            false);
+        methodVisitor.visitVarInsn(ALOAD, 9);
+        methodVisitor.visitInsn(ATHROW);
+        methodVisitor.visitLabel(label10);
+        methodVisitor.visitLineNumber(73, label10);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            1,
+            new Object[] {"kotlinx/coroutines/channels/ClosedReceiveChannelException"});
+        methodVisitor.visitVarInsn(ASTORE, 3);
+        methodVisitor.visitLabel(label42);
+        methodVisitor.visitLineNumber(75, label42);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              "kotlinx/coroutines/CoroutineScope",
+              "java/lang/Object",
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitFieldInsn(GETSTATIC, "kotlin/Unit", "INSTANCE", "Lkotlin/Unit;");
+        methodVisitor.visitInsn(ARETURN);
+        methodVisitor.visitLabel(label15);
+        methodVisitor.visitFrame(
+            Opcodes.F_FULL,
+            11,
+            new Object[] {
+              "io/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1",
+              "java/lang/Object",
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              Opcodes.TOP,
+              "java/lang/Object"
+            },
+            0,
+            new Object[] {});
+        methodVisitor.visitTypeInsn(NEW, "java/lang/IllegalStateException");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitLdcInsn("call to 'resume' before 'invoke' with coroutine");
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "java/lang/IllegalStateException",
+            "<init>",
+            "(Ljava/lang/String;)V",
+            false);
+        methodVisitor.visitInsn(ATHROW);
+        methodVisitor.visitLocalVariable(
+            "$this$launch", "Lkotlinx/coroutines/CoroutineScope;", null, label9, label13, 2);
+        methodVisitor.visitLocalVariable(
+            "$this$launch", "Lkotlinx/coroutines/CoroutineScope;", null, label3, label14, 2);
+        methodVisitor.visitLocalVariable(
+            "$this$launch", "Lkotlinx/coroutines/CoroutineScope;", null, label5, label23, 2);
+        methodVisitor.visitLocalVariable(
+            "$this$consume$iv",
+            "Lkotlinx/coroutines/channels/ReceiveChannel;",
+            null,
+            label18,
+            label13,
+            3);
+        methodVisitor.visitLocalVariable(
+            "$this$consume$iv",
+            "Lkotlinx/coroutines/channels/ReceiveChannel;",
+            null,
+            label34,
+            label14,
+            3);
+        methodVisitor.visitLocalVariable(
+            "$this$consume$iv",
+            "Lkotlinx/coroutines/channels/ReceiveChannel;",
+            null,
+            label39,
+            label40,
+            3);
+        methodVisitor.visitLocalVariable(
+            "$this$consume$iv",
+            "Lkotlinx/coroutines/channels/ReceiveChannel;",
+            null,
+            label40,
+            label41,
+            3);
+        methodVisitor.visitLocalVariable(
+            "$this$consume$iv",
+            "Lkotlinx/coroutines/channels/ReceiveChannel;",
+            null,
+            label2,
+            label10,
+            3);
+        methodVisitor.visitLocalVariable(
+            "cause$iv", "Ljava/lang/Throwable;", null, label0, label13, 6);
+        methodVisitor.visitLocalVariable(
+            "cause$iv", "Ljava/lang/Throwable;", null, label34, label14, 6);
+        methodVisitor.visitLocalVariable(
+            "cause$iv", "Ljava/lang/Throwable;", null, label39, label40, 6);
+        methodVisitor.visitLocalVariable(
+            "cause$iv", "Ljava/lang/Throwable;", null, label40, label41, 6);
+        methodVisitor.visitLocalVariable(
+            "cause$iv", "Ljava/lang/Throwable;", null, label2, label44, 6);
+        methodVisitor.visitLocalVariable(
+            "cause$iv", "Ljava/lang/Throwable;", null, label44, label10, 6);
+        methodVisitor.visitLocalVariable(
+            "buffer", "Ljava/nio/ByteBuffer;", null, label25, label27, 8);
+        methodVisitor.visitLocalVariable(
+            "buffer", "Ljava/nio/ByteBuffer;", null, label26, label4, 8);
+        methodVisitor.visitLocalVariable("e$iv", "Ljava/lang/Throwable;", null, label43, label7, 9);
+        methodVisitor.visitLocalVariable(
+            "$i$a$-consume-JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1$1",
+            "I",
+            null,
+            label22,
+            label13,
+            7);
+        methodVisitor.visitLocalVariable("$i$f$consume", "I", null, label19, label13, 5);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lio/ktor/client/engine/java/JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1;",
+            null,
+            label16,
+            label15,
+            0);
+        methodVisitor.visitLocalVariable(
+            "$result", "Ljava/lang/Object;", null, label16, label15, 1);
+        methodVisitor.visitLocalVariable(
+            "$i$a$-consume-JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1$1",
+            "I",
+            null,
+            label33,
+            label14,
+            7);
+        methodVisitor.visitLocalVariable("$i$f$consume", "I", null, label32, label14, 5);
+        methodVisitor.visitLocalVariable(
+            "$i$a$-consume-JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber$1$1",
+            "I",
+            null,
+            label38,
+            label40,
+            7);
+        methodVisitor.visitLocalVariable("$i$f$consume", "I", null, label37, label10, 5);
+        methodVisitor.visitMaxs(5, 11);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(70, label0);
+        methodVisitor.visitTypeInsn(
+            NEW, "com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest$Main");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest$Main",
+            "<init>",
+            "()V",
+            false);
+        methodVisitor.visitInsn(ACONST_NULL);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest$Main",
+            "invokeSuspend",
+            "(Ljava/lang/Object;)Ljava/lang/Object;",
+            false);
+        methodVisitor.visitInsn(POP);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(71, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java
index 1fcabcd..b16a586 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokeMethodHandleRuntimeErrorTest.java
@@ -66,10 +66,6 @@
         .addProgramClasses(Main.class, I.class, Super.class)
         .addProgramClassFileData(getInvokeCustomTransform())
         .setMinApi(parameters.getApiLevel())
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               if (hasCompileSupport()) {
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvokePolymorphicTypesTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokePolymorphicTypesTest.java
new file mode 100644
index 0000000..58de21e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvokePolymorphicTypesTest.java
@@ -0,0 +1,280 @@
+// 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.cf.methodhandles;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * This test is a refactoring of the InvokePolymorphic test code run by the various AndroidOTest
+ * runners.
+ */
+@RunWith(Parameterized.class)
+public class InvokePolymorphicTypesTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "N-1-string",
+          "2-a--1-1.1-2.24-12345678-N-1-string",
+          "false",
+          "h",
+          "56",
+          "72",
+          "2147483689",
+          "0.56",
+          "100.0",
+          "hello",
+          "goodbye",
+          "true");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InvokePolymorphicTypesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    boolean hasCompileSupport =
+        parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+    boolean hasRuntimeSupport =
+        parameters.isCfRuntime()
+            || parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V8_1_0);
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Data.class, TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            hasCompileSupport,
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            hasRuntimeSupport,
+            r ->
+                r.assertSuccess()
+                    .assertStderrMatches(
+                        containsString(
+                            "Instruction is unrepresentable in DEX V35: invoke-polymorphic")),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().equals(AndroidApiLevel.B));
+    boolean hasCompileSupport =
+        parameters.isCfRuntime()
+            || parameters
+                .getApiLevel()
+                .isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+    boolean hasRuntimeSupport =
+        parameters.isCfRuntime()
+            || parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V8_1_0);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Data.class, TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .applyIf(
+            !hasCompileSupport,
+            b ->
+                b.addDontWarn(
+                    MethodType.class,
+                    MethodHandle.class,
+                    MethodHandles.class,
+                    MethodHandles.Lookup.class))
+        .addKeepMainRule(TestClass.class)
+        .addKeepMethodRules(
+            Reference.methodFromMethod(Data.class.getDeclaredConstructor()),
+            Reference.methodFromMethod(
+                TestClass.class.getDeclaredMethod(
+                    "buildString", Integer.class, int.class, String.class)),
+            Reference.methodFromMethod(
+                TestClass.class.getDeclaredMethod(
+                    "buildString",
+                    byte.class,
+                    char.class,
+                    short.class,
+                    float.class,
+                    double.class,
+                    long.class,
+                    Integer.class,
+                    int.class,
+                    String.class)),
+            Reference.methodFromMethod(
+                TestClass.class.getDeclaredMethod(
+                    "testWithAllTypes",
+                    boolean.class,
+                    char.class,
+                    short.class,
+                    int.class,
+                    long.class,
+                    float.class,
+                    double.class,
+                    String.class,
+                    Object.class)))
+        .allowDiagnosticMessages()
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            hasCompileSupport,
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            hasRuntimeSupport,
+            r ->
+                r.assertSuccess()
+                    .assertStderrMatches(
+                        containsString(
+                            "Instruction is unrepresentable in DEX V35: invoke-polymorphic")),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  static class Data {}
+
+  static class TestClass {
+
+    public String buildString(Integer i1, int i2, String s) {
+      return (i1 == null ? "N" : "!N") + "-" + i2 + "-" + s;
+    }
+
+    public void testInvokePolymorphic() {
+      MethodType mt = MethodType.methodType(String.class, Integer.class, int.class, String.class);
+      MethodHandles.Lookup lk = MethodHandles.lookup();
+
+      try {
+        MethodHandle mh = lk.findVirtual(getClass(), "buildString", mt);
+        System.out.println(mh.invoke(this, null, 1, "string"));
+      } catch (Throwable t) {
+        t.printStackTrace();
+      }
+    }
+
+    public String buildString(
+        byte b, char c, short s, float f, double d, long l, Integer i1, int i2, String str) {
+      return b
+          + "-"
+          + c
+          + "-"
+          + s
+          + "-"
+          + f
+          + "-"
+          + d
+          + "-"
+          + l
+          + "-"
+          + (i1 == null ? "N" : "!N")
+          + "-"
+          + i2
+          + "-"
+          + str;
+    }
+
+    public void testInvokePolymorphicRange() {
+      MethodType mt =
+          MethodType.methodType(
+              String.class,
+              byte.class,
+              char.class,
+              short.class,
+              float.class,
+              double.class,
+              long.class,
+              Integer.class,
+              int.class,
+              String.class);
+      MethodHandles.Lookup lk = MethodHandles.lookup();
+
+      try {
+        MethodHandle mh = lk.findVirtual(getClass(), "buildString", mt);
+        System.out.println(
+            mh.invoke(
+                this, (byte) 2, 'a', (short) 0xFFFF, 1.1f, 2.24d, 12345678L, null, 1, "string"));
+      } catch (Throwable t) {
+        t.printStackTrace();
+      }
+    }
+
+    public static void testWithAllTypes(
+        boolean z, char a, short b, int c, long d, float e, double f, String g, Object h) {
+      System.out.println(z);
+      System.out.println(a);
+      System.out.println(b);
+      System.out.println(c);
+      System.out.println(d);
+      System.out.println(e);
+      System.out.println(f);
+      System.out.println(g);
+      System.out.println(h);
+    }
+
+    public void testInvokePolymorphicWithAllTypes() {
+      try {
+        MethodHandle mth =
+            MethodHandles.lookup()
+                .findStatic(
+                    TestClass.class,
+                    "testWithAllTypes",
+                    MethodType.methodType(
+                        void.class,
+                        boolean.class,
+                        char.class,
+                        short.class,
+                        int.class,
+                        long.class,
+                        float.class,
+                        double.class,
+                        String.class,
+                        Object.class));
+        mth.invokeExact(
+            false,
+            'h',
+            (short) 56,
+            72,
+            Integer.MAX_VALUE + 42l,
+            0.56f,
+            100.0d,
+            "hello",
+            (Object) "goodbye");
+      } catch (Throwable t) {
+        t.printStackTrace();
+      }
+    }
+
+    public MethodHandle testInvokePolymorphicWithConstructor() {
+      MethodHandle mh = null;
+      MethodType mt = MethodType.methodType(void.class);
+      MethodHandles.Lookup lk = MethodHandles.lookup();
+
+      try {
+        mh = lk.findConstructor(Data.class, mt);
+        System.out.println(mh.invoke().getClass() == Data.class);
+      } catch (Throwable t) {
+        t.printStackTrace();
+      }
+
+      return mh;
+    }
+
+    public static void main(String[] args) {
+      TestClass invokePolymorphic = new TestClass();
+      invokePolymorphic.testInvokePolymorphic();
+      invokePolymorphic.testInvokePolymorphicRange();
+      invokePolymorphic.testInvokePolymorphicWithAllTypes();
+      invokePolymorphic.testInvokePolymorphicWithConstructor();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
index 497e19f..fbea211 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.cf.methodhandles;
 
-import com.android.tools.r8.NoVerticalClassMerging;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
@@ -61,7 +60,6 @@
     // Class that is only mentioned in return value of LDC(MethodType)-instruction.
   }
 
-  @NoVerticalClassMerging
   public interface I {
 
     static void svi(int i) {
@@ -126,8 +124,8 @@
       divicMethod().invoke(i, 15, 'x');
       assertEquals(42L, (long) dijicMethod().invoke(i, 16, 'x'));
       constructorMethod().invoke(21);
-      System.out.println(veType().parameterType(0).getName().lastIndexOf('.'));
-      System.out.println(fType().returnType().getName().lastIndexOf('.'));
+      System.out.println(veType().parameterType(0).equals(E.class));
+      System.out.println(fType().returnType().equals(F.class));
     } catch (Throwable e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java
index ae44ac9..3189837 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/MethodHandleTestRunner.java
@@ -5,17 +5,16 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.cf.methodhandles.MethodHandleTest.C;
 import com.android.tools.r8.cf.methodhandles.MethodHandleTest.E;
 import com.android.tools.r8.cf.methodhandles.MethodHandleTest.F;
@@ -25,7 +24,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
-import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,49 +39,25 @@
     CONSTANT,
   }
 
-  enum MinifyMode {
-    NONE,
-    MINIFY,
-  }
-
   private String getExpected() {
     return StringUtils.lines(
         "C 42", "svi 1", "sji 2", "svic 3", "sjic 4", "vvi 5", "vji 6", "vvic 7", "vjic 8", "svi 9",
-        "sji 10", "svic 11", "sjic 12", "dvi 13", "dji 14", "dvic 15", "djic 16", "C 21", "37",
-        "37");
+        "sji 10", "svic 11", "sjic 12", "dvi 13", "dji 14", "dvic 15", "djic 16", "C 21", "true",
+        "true");
   }
 
   private final TestParameters parameters;
   private final LookupType lookupType;
-  private final MinifyMode minifyMode;
 
-  @Parameters(name = "{0}, lookup:{1}, minify:{2}")
+  @Parameters(name = "{0}, lookup:{1}")
   public static List<Object[]> data() {
-    List<Object[]> res = new ArrayList<>();
-    for (TestParameters params :
-        TestParameters.builder()
-            .withCfRuntimes()
-            .withDexRuntimesStartingFromExcluding(Version.V7_0_0)
-            // .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
-            .withAllApiLevels()
-            .build()) {
-      for (LookupType lookupType : LookupType.values()) {
-        for (MinifyMode minifyMode : MinifyMode.values()) {
-          if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
-            // Skip because we don't keep the members looked up dynamically.
-            continue;
-          }
-          res.add(new Object[] {params, lookupType.name(), minifyMode.name()});
-        }
-      }
-    }
-    return res;
+    return buildParameters(
+        TestParameters.builder().withAllRuntimesAndApiLevels().build(), LookupType.values());
   }
 
-  public MethodHandleTestRunner(TestParameters parameters, String lookupType, String minifyMode) {
+  public MethodHandleTestRunner(TestParameters parameters, LookupType lookupType) {
     this.parameters = parameters;
-    this.lookupType = LookupType.valueOf(lookupType);
-    this.minifyMode = MinifyMode.valueOf(minifyMode);
+    this.lookupType = lookupType;
   }
 
   @Test
@@ -98,61 +72,38 @@
 
   @Test
   public void testD8() throws Exception {
-    assumeTrue(parameters.isDexRuntime() && minifyMode == MinifyMode.NONE);
+    assumeTrue(parameters.isDexRuntime());
     testForD8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(getInputClasses())
         .addProgramClassFileData(getTransformedClasses())
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), CLASS.getName())
         .apply(this::checkResult);
   }
 
   @Test
+  public void testKeepAllR8() throws Exception {
+    // For the dynamic case, method handles/types are created reflectively so keep all.
+    runR8(TestShrinkerBuilder::addKeepAllClassesRule);
+  }
+
+  @Test
   public void testR8() throws Exception {
-    R8TestBuilder<?> builder =
-        testForR8(parameters.getBackend())
-            .setMinApi(parameters.getApiLevel())
-            .addProgramClasses(getInputClasses())
-            .addProgramClassFileData(getTransformedClasses())
-            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-            .addNoVerticalClassMergingAnnotations()
-            // TODO(b/238175192): remove again when resolved
-            .addOptionsModification(
-                options -> options.enableUnrepresentableInDexInstructionRemoval = true);
-    if (minifyMode == MinifyMode.MINIFY) {
-      builder
-          .enableProguardTestOptions()
-          .addKeepMainRule(MethodHandleTest.class)
-          .addKeepRules(
-              // Prevent the second argument of C.svic(), C.sjic(), I.sjic() and I.svic() from
-              // being removed although they are never used unused. This is needed since these
-              // methods are accessed reflectively.
-              "-keep,allowobfuscation public class " + typeName(C.class) + " {",
-              "  static void svic(int, char);",
-              "  static long sjic(int, char);",
-              "}",
-              "-keep,allowobfuscation public interface " + typeName(I.class) + " {",
-              "  static long sjic(int, char);",
-              "  static void svic(int, char);",
-              "}");
-      // TODO(b/235810300): The compiler fails with assertion in AppInfoWithLiveness.
-      if (lookupType == LookupType.CONSTANT && hasConstMethodCompileSupport()) {
-        builder.allowDiagnosticMessages();
-        assertThrows(CompilationFailedException.class, builder::compile);
-        return;
-      }
-    } else {
-      builder.noTreeShaking();
-      builder.noMinification();
-    }
-    builder
+    // We can't shrink the DYNAMIC variant as the methods are looked up reflectively.
+    assumeTrue(lookupType == LookupType.CONSTANT);
+    runR8(b -> {});
+  }
+
+  private void runR8(ThrowableConsumer<R8FullTestBuilder> additionalSetUp) throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(additionalSetUp)
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(getInputClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+        .addKeepMainRule(MethodHandleTest.class)
         .allowDiagnosticMessages()
-        .mapUnsupportedFeaturesToWarnings()
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), CLASS.getCanonicalName())
         .apply(this::checkResult);
@@ -168,6 +119,14 @@
         || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
   }
 
+  private boolean hasMethodHandlesRuntimeSupport() {
+    return parameters.isCfRuntime()
+        || parameters
+            .asDexRuntime()
+            .maxSupportedApiLevel()
+            .isGreaterThanOrEqualTo(apiLevelWithInvokePolymorphicSupport());
+  }
+
   private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
     if ((lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport())
         || (lookupType == LookupType.CONSTANT && !hasConstMethodCompileSupport())) {
@@ -180,6 +139,12 @@
   }
 
   private void checkResult(TestRunResult<?> result) {
+    if (lookupType == LookupType.DYNAMIC && !hasMethodHandlesRuntimeSupport()) {
+      result
+          .assertFailureWithErrorThatThrows(NoClassDefFoundError.class)
+          .assertStderrMatches(containsString("java.lang.invoke.MethodHandles"));
+      return;
+    }
     if (lookupType == LookupType.DYNAMIC && !hasInvokePolymorphicCompileSupport()) {
       result
           .assertFailureWithErrorThatThrows(RuntimeException.class)
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java
index 962f964..ce9cae3 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/VarHandleTest.java
@@ -93,10 +93,6 @@
     testForD8()
         .addProgramFiles(getProgramInputs())
         .setMinApi(parameters.getApiLevel())
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               if (hasInvokePolymorphicCompileSupport()) {
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java
index aac5a20..daf61c4 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/ClassFieldMethodHandleTest.java
@@ -72,10 +72,6 @@
         .addProgramClasses(C.class)
         .addProgramClassFileData(getTransformedMain())
         .setMinApi(parameters.getApiLevel())
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
@@ -90,10 +86,6 @@
         .addProgramClassFileData(getTransformedMain())
         .setMinApi(parameters.getApiLevel())
         .allowDiagnosticMessages()
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java
index 629a737..927b27e 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/fields/InterfaceFieldMethodHandleTest.java
@@ -75,10 +75,6 @@
         .addProgramClasses(I.class)
         .addProgramClassFileData(getTransformedMain())
         .setMinApi(parameters.getApiLevel())
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
@@ -93,10 +89,6 @@
         .addProgramClassFileData(getTransformedMain())
         .setMinApi(parameters.getApiLevel())
         .allowDiagnosticMessages()
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
index 70d3aec..7453e9b 100644
--- a/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/invokespecial/InvokeSpecialMethodHandleTest.java
@@ -75,10 +75,6 @@
         .addProgramClasses(C.class, Main.class)
         .addProgramClassFileData(getTransformedD())
         .setMinApi(parameters.getApiLevel())
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
@@ -93,10 +89,6 @@
         .addProgramClassFileData(getTransformedD())
         .setMinApi(parameters.getApiLevel())
         .allowDiagnosticMessages()
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(this::checkDiagnostics)
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkResult);
diff --git a/src/test/java/com/android/tools/r8/code/invokedynamic/CompileGuavaWithUnrepresentableRewritingTest.java b/src/test/java/com/android/tools/r8/code/invokedynamic/CompileGuavaWithUnrepresentableRewritingTest.java
index 655675e..a1c821e 100644
--- a/src/test/java/com/android/tools/r8/code/invokedynamic/CompileGuavaWithUnrepresentableRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/code/invokedynamic/CompileGuavaWithUnrepresentableRewritingTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.code.invokedynamic;
 
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
@@ -13,6 +13,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.errors.UnsupportedInvokeCustomDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,25 +36,21 @@
   @Test
   public void testD8DexNoDesugaring() throws Throwable {
     assumeTrue(ToolHelper.isTestingR8Lib());
-    // TODO(b/238175192): We should not generate invalid bytecode
     assertThrows(
         CompilationFailedException.class,
         () -> {
           testForD8(Backend.DEX)
+              // DEPS contains all R8 dependencies, including guava, which extends the surface
+              // of UnrepresentableRewriting.
               .addProgramFiles(ToolHelper.DEPS)
               .setMinApi(AndroidApiLevel.B)
               .disableDesugaring()
-              .mapUnsupportedFeaturesToWarnings()
-              // TODO(b/238175192): remove again when resolved
-              .addOptionsModification(
-                  options -> options.enableUnrepresentableInDexInstructionRemoval = true)
               .compileWithExpectedDiagnostics(
                   diagnostics ->
                       diagnostics
-                          .assertErrorMessageThatMatches(
-                              containsString("Expected stack type to be single width"))
-                          .assertWarningMessageThatMatches(
-                              containsString("Invalid stack map table at instruction")));
+                          .assertAllWarningsMatch(
+                              diagnosticType(UnsupportedInvokeCustomDiagnostic.class))
+                          .assertErrorThatMatches(diagnosticType(DexFileOverflowDiagnostic.class)));
         });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java
index 1fa8060..a7acf9e 100644
--- a/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java
+++ b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeCustomRuntimeErrorTest.java
@@ -88,10 +88,6 @@
         .addProgramClassFileData(getTransformedTestClass())
         .setMinApi(parameters.getApiLevel())
         .disableDesugaring()
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics
@@ -112,9 +108,6 @@
         .addProgramClasses(I.class, A.class)
         .addProgramClassFileData(getTransformedTestClass())
         .setMinApi(minApi)
-        .mapUnsupportedFeaturesToWarnings()
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               if (expectedSuccess) {
diff --git a/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest.java
index c2caab7..7494b13 100644
--- a/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest.java
@@ -71,9 +71,6 @@
         .addProgramClassFileData(getTransformedTestClass())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(TestClass.class)
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .addKeepRules("-keepclassmembers class * { *** foo(...); }")
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
index 2dde386..9525784 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
@@ -66,9 +66,6 @@
                         || diagnostic instanceof ConstantDynamicDesugarDiagnostic)
                     ? DiagnosticsLevel.WARNING
                     : level)
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertWarningsMatch(
@@ -107,10 +104,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .allowDiagnosticWarningMessages()
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               if (parameters.isDexRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
index 88328c7..486b958 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
@@ -90,9 +90,6 @@
                         || diagnostic instanceof UnsupportedFeatureDiagnostic)
                     ? DiagnosticsLevel.WARNING
                     : level)
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics
@@ -124,9 +121,6 @@
                         || diagnostic instanceof UnsupportedFeatureDiagnostic)
                     ? DiagnosticsLevel.WARNING
                     : level)
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               diagnostics.assertOnlyWarnings();
@@ -172,10 +166,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_CLASS)
         .allowDiagnosticMessages()
-        .mapUnsupportedFeaturesToWarnings()
-        // TODO(b/238175192): remove again when resolved
-        .addOptionsModification(
-            options -> options.enableUnrepresentableInDexInstructionRemoval = true)
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               diagnostics
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 8fc32b2..3737814 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
@@ -11,11 +11,11 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.GenerateLintFiles;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 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.GenerateLintFiles;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.WrapperDescriptor;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -210,11 +211,13 @@
             null,
             false,
             minApi.getLevel());
+
+    DexApplication app =
+        libraryDesugaringSpecification.getAppForTesting(
+            new InternalOptions(factory, new Reporter()), spec.isLibraryCompilation());
     MachineDesugaredLibrarySpecification specification =
-        spec.toMachineSpecification(
-            new InternalOptions(factory, new Reporter()),
-            libraryDesugaringSpecification.getLibraryFiles(),
-            Timing.empty());
+        spec.toMachineSpecification(app, Timing.empty());
+
     Set<String> wrappersInSpec =
         specification.getWrappers().keySet().stream()
             .map(DexType::toString)
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 3c4a223..341ab5a 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
@@ -10,7 +10,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.GenerateLintFiles;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -18,6 +17,7 @@
 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.GenerateLintFiles;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -37,6 +37,8 @@
 
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
 
+  private List<String> lintContents;
+
   @Parameters(name = "{0}, spec: {1}")
   public static List<Object[]> data() {
     return buildParameters(getTestParameters().withNoneRuntime().build(), getJdk8Jdk11());
@@ -48,78 +50,105 @@
     this.libraryDesugaringSpecification = libraryDesugaringSpecification;
   }
 
+  private boolean supportsAllMethodsOf(String type) {
+    return lintContents.contains(type);
+  }
+
+  private boolean supportsMethodButNotAllMethodsInClass(String method) {
+    assert method.contains("#");
+    return !supportsAllMethodsOf(method.split("#")[0]) && lintContents.contains(method);
+  }
+
   private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
     // Just do some light probing in the generated lint files.
-    List<String> methods = FileUtils.readAllLines(lintFile);
+    lintContents = FileUtils.readAllLines(lintFile);
 
     // All methods supported on Optional*.
-    assertTrue(methods.contains("java/util/Optional"));
-    assertTrue(methods.contains("java/util/OptionalInt"));
-
-    // ConcurrentHashMap is fully supported on JDK 11.
-    assertEquals(
-        libraryDesugaringSpecification != JDK8,
-        methods.contains("java/util/concurrent/ConcurrentHashMap"));
+    assertTrue(supportsAllMethodsOf("java/util/Optional"));
+    assertTrue(supportsAllMethodsOf("java/util/OptionalInt"));
 
     // No parallel* methods pre L, and all stream methods supported from L.
     assertEquals(
         minApiLevel == AndroidApiLevel.L,
-        methods.contains("java/util/Collection#parallelStream()Ljava/util/stream/Stream;"));
+        supportsMethodButNotAllMethodsInClass(
+            "java/util/Collection#parallelStream()Ljava/util/stream/Stream;"));
     assertEquals(
-        minApiLevel == AndroidApiLevel.L, methods.contains("java/util/stream/DoubleStream"));
+        minApiLevel == AndroidApiLevel.L, supportsAllMethodsOf("java/util/stream/DoubleStream"));
     assertFalse(
-        methods.contains(
+        supportsMethodButNotAllMethodsInClass(
             "java/util/stream/DoubleStream#parallel()Ljava/util/stream/DoubleStream;"));
     assertFalse(
-        methods.contains("java/util/stream/DoubleStream#parallel()Ljava/util/stream/BaseStream;"));
+        supportsMethodButNotAllMethodsInClass(
+            "java/util/stream/DoubleStream#parallel()Ljava/util/stream/BaseStream;"));
     assertEquals(
         minApiLevel == AndroidApiLevel.B,
-        methods.contains(
+        supportsMethodButNotAllMethodsInClass(
             "java/util/stream/DoubleStream#allMatch(Ljava/util/function/DoublePredicate;)Z"));
-    assertEquals(minApiLevel == AndroidApiLevel.L, methods.contains("java/util/stream/IntStream"));
+    assertEquals(
+        minApiLevel == AndroidApiLevel.L, lintContents.contains("java/util/stream/IntStream"));
     assertFalse(
-        methods.contains("java/util/stream/IntStream#parallel()Ljava/util/stream/IntStream;"));
+        supportsMethodButNotAllMethodsInClass(
+            "java/util/stream/IntStream#parallel()Ljava/util/stream/IntStream;"));
     assertFalse(
-        methods.contains("java/util/stream/IntStream#parallel()Ljava/util/stream/BaseStream;"));
+        supportsMethodButNotAllMethodsInClass(
+            "java/util/stream/IntStream#parallel()Ljava/util/stream/BaseStream;"));
     assertEquals(
         minApiLevel == AndroidApiLevel.B,
-        methods.contains(
+        supportsMethodButNotAllMethodsInClass(
             "java/util/stream/IntStream#allMatch(Ljava/util/function/IntPredicate;)Z"));
 
-    if (libraryDesugaringSpecification != JDK8) {
-      // TODO(b/203382252): Investigate why the following assertions are not working on JDK 11.
-      return;
+    assertEquals(
+        libraryDesugaringSpecification != JDK8,
+        supportsAllMethodsOf("java/util/concurrent/ConcurrentHashMap"));
+
+    // Checks specific methods are supported or not in JDK8, all is supported in JDK11.
+    if (libraryDesugaringSpecification == JDK8) {
+      // Supported methods on ConcurrentHashMap.
+      assertTrue(
+          supportsMethodButNotAllMethodsInClass(
+              "java/util/concurrent/ConcurrentHashMap#getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"));
+
+      // Don't include constructors.
+      assertFalse(
+          supportsMethodButNotAllMethodsInClass(
+              "java/util/concurrent/ConcurrentHashMap#<init>()V"));
+
+      // Unsupported methods on ConcurrentHashMap.
+      assertFalse(
+          supportsMethodButNotAllMethodsInClass(
+              "java/util/concurrent/ConcurrentHashMap#reduce(JLjava/util/function/BiFunction;Ljava/util/function/BiFunction;)Ljava/lang/Object;"));
+      assertFalse(
+          supportsMethodButNotAllMethodsInClass(
+              "java/util/concurrent/ConcurrentHashMap#newKeySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;"));
     }
 
-    // Supported methods on ConcurrentHashMap.
+    // Maintain type.
+    assertEquals(
+        libraryDesugaringSpecification != JDK8,
+        supportsAllMethodsOf("java/io/UncheckedIOException"));
+
+    // Retarget method.
     assertTrue(
-        methods.contains(
-            "java/util/concurrent/ConcurrentHashMap#getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"));
-
-    // Don't include constructors.
-    assertFalse(methods.contains("java/util/concurrent/ConcurrentHashMap#<init>()V"));
-
-    // Unsupported methods on ConcurrentHashMap.
-    assertFalse(
-        methods.contains(
-            "java/util/concurrent/ConcurrentHashMap#reduce(JLjava/util/function/BiFunction;Ljava/util/function/BiFunction;)Ljava/lang/Object;"));
-    assertFalse(
-        methods.contains(
-            "java/util/concurrent/ConcurrentHashMap#newKeySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;"));
+        supportsMethodButNotAllMethodsInClass(
+            "java/util/Arrays#spliterator([I)Ljava/util/Spliterator$OfInt;"));
 
     // Emulated interface default method.
-    assertTrue(methods.contains("java/util/List#spliterator()Ljava/util/Spliterator;"));
+    assertTrue(
+        supportsMethodButNotAllMethodsInClass(
+            "java/util/List#spliterator()Ljava/util/Spliterator;"));
 
     // Emulated interface static method.
-    assertTrue(methods.contains("java/util/Map$Entry#comparingByValue()Ljava/util/Comparator;"));
+    assertTrue(
+        supportsMethodButNotAllMethodsInClass(
+            "java/util/Map$Entry#comparingByValue()Ljava/util/Comparator;"));
 
     // No no-default method from emulated interface.
-    assertFalse(methods.contains("java/util/List#size()I"));
+    assertFalse(supportsMethodButNotAllMethodsInClass("java/util/List#size()I"));
 
     // File should be sorted.
-    List<String> sorted = new ArrayList<>(methods);
+    List<String> sorted = new ArrayList<>(lintContents);
     sorted.sort(Comparator.naturalOrder());
-    assertEquals(methods, sorted);
+    assertEquals(lintContents, sorted);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/TelemetryMapInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/TelemetryMapInterfaceTest.java
new file mode 100644
index 0000000..174c311
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/TelemetryMapInterfaceTest.java
@@ -0,0 +1,81 @@
+// 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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction for b/202188674.
+@RunWith(Parameterized.class)
+public class TelemetryMapInterfaceTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("k=key1;v=value1", "k=key1;v=value1", "k=key1;v=value1", "k=key1;v=value1");
+
+  private final TestParameters parameters;
+  private final CompilationSpecification compilationSpecification;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        getJdk8Jdk11(),
+        SPECIFICATIONS_WITH_CF2CF);
+  }
+
+  public TelemetryMapInterfaceTest(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.compilationSpecification = compilationSpecification;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+  }
+
+  @Test
+  public void testMap() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Concrete concrete = new Concrete();
+      concrete.put("key1", "value1");
+      concrete.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+      MapInterface mapInterface = concrete;
+      mapInterface.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+      HashMap<String, Object> hashMap = concrete;
+      hashMap.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+      Map<String, Object> map = concrete;
+      map.forEach((k, v) -> System.out.println("k=" + k + ";v=" + v));
+    }
+  }
+
+  interface MapInterface {
+
+    void forEach(BiConsumer<? super String, ? super Object> consumer);
+  }
+
+  static class Concrete extends HashMap<String, Object> implements MapInterface {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
index 7c98dbe..cd5c568 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.specification;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.StringResource;
@@ -13,7 +15,10 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecification;
@@ -21,8 +26,17 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.MultiAPILevelHumanDesugaredLibrarySpecificationParser;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.MultiAPILevelLegacyDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineTopLevelFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -46,7 +60,7 @@
   }
 
   @Test
-  public void testMultiLevel() throws IOException {
+  public void testMultiLevelLegacy() throws IOException {
     Assume.assumeTrue(ToolHelper.isLocalDevelopment());
 
     LibraryDesugaringSpecification legacySpec = LibraryDesugaringSpecification.JDK8;
@@ -61,9 +75,10 @@
                 options.dexItemFactory(), options.reporter)
             .parseMultiLevelConfiguration(StringResource.fromFile(legacySpec.getSpecification()));
 
+    DexApplication app = legacySpec.getAppForTesting(options, true);
+
     MultiAPILevelHumanDesugaredLibrarySpecification humanSpec1 =
-        converter.convertAllAPILevels(
-            spec, legacySpec.getDesugarJdkLibs(), legacySpec.getLibraryFiles(), options);
+        converter.convertAllAPILevels(spec, app);
 
     Box<String> json = new Box<>();
     MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.export(
@@ -74,6 +89,75 @@
             .parseMultiLevelConfiguration(StringResource.fromString(json.get(), Origin.unknown()));
 
     assertSpecEquals(humanSpec1, humanSpec2);
+
+    Box<String> json2 = new Box<>();
+    HumanToMachineSpecificationConverter converter2 =
+        new HumanToMachineSpecificationConverter(Timing.empty());
+    MultiAPILevelMachineDesugaredLibrarySpecification machineSpec1 =
+        converter2.convertAllAPILevels(humanSpec2, app);
+    MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
+        machineSpec1, (string, handler) -> json2.set(string), options.dexItemFactory());
+
+    MachineDesugaredLibrarySpecification machineSpecParsed =
+        new MachineDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(),
+                options.reporter,
+                true,
+                AndroidApiLevel.B.getLevel(),
+                new SyntheticNaming())
+            .parse(StringResource.fromString(json2.get(), Origin.unknown()));
+    assertFalse(machineSpecParsed.getRewriteType().isEmpty());
+  }
+
+  @Test
+  public void testSingleLevel() throws IOException {
+    Assume.assumeTrue(ToolHelper.isLocalDevelopment());
+
+    LibraryDesugaringSpecification humanSpec = LibraryDesugaringSpecification.JDK11_PATH;
+
+    InternalOptions options = new InternalOptions();
+
+    DexApplication app = humanSpec.getAppForTesting(options, true);
+
+    MultiAPILevelHumanDesugaredLibrarySpecification humanSpec2 =
+        new MultiAPILevelHumanDesugaredLibrarySpecificationParser(
+                options.dexItemFactory(), options.reporter)
+            .parseMultiLevelConfiguration(StringResource.fromFile(humanSpec.getSpecification()));
+
+    Box<String> json2 = new Box<>();
+    HumanToMachineSpecificationConverter converter2 =
+        new HumanToMachineSpecificationConverter(Timing.empty());
+    MultiAPILevelMachineDesugaredLibrarySpecification machineSpec1 =
+        converter2.convertAllAPILevels(humanSpec2, app);
+    MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
+        machineSpec1, (string, handler) -> json2.set(string), options.dexItemFactory());
+
+    for (AndroidApiLevel api :
+        new AndroidApiLevel[] {AndroidApiLevel.B, AndroidApiLevel.N, AndroidApiLevel.O}) {
+      MachineDesugaredLibrarySpecification machineSpecParsed =
+          new MachineDesugaredLibrarySpecificationParser(
+                  options.dexItemFactory(),
+                  options.reporter,
+                  true,
+                  api.getLevel(),
+                  new SyntheticNaming())
+              .parse(StringResource.fromString(json2.get(), Origin.unknown()));
+
+      HumanDesugaredLibrarySpecification humanSpecB =
+          new HumanDesugaredLibrarySpecificationParser(
+                  options.dexItemFactory(), options.reporter, true, api.getLevel())
+              .parse(StringResource.fromFile(humanSpec.getSpecification()));
+      MachineDesugaredLibrarySpecification machineSpecConverted =
+          humanSpecB.toMachineSpecification(app, Timing.empty());
+
+      assertSpecEquals(machineSpecConverted, machineSpecParsed);
+    }
+  }
+
+  private void assertSpecEquals(
+      MachineDesugaredLibrarySpecification spec1, MachineDesugaredLibrarySpecification spec2) {
+    assertTopLevelFlagsEquals(spec1.getTopLevelFlags(), spec2.getTopLevelFlags());
+    assertFlagsEquals(spec1.getRewritingFlags(), spec2.getRewritingFlags());
   }
 
   private void assertSpecEquals(
@@ -96,6 +180,46 @@
   }
 
   private void assertFlagsEquals(
+      MachineRewritingFlags rewritingFlags1, MachineRewritingFlags rewritingFlags2) {
+    assertEquals(rewritingFlags1.getRewriteType(), rewritingFlags2.getRewriteType());
+    assertEquals(rewritingFlags1.getMaintainType(), rewritingFlags2.getMaintainType());
+    assertEquals(
+        rewritingFlags1.getRewriteDerivedTypeOnly(), rewritingFlags2.getRewriteDerivedTypeOnly());
+    assertEquals(
+        rewritingFlags1.getStaticFieldRetarget(), rewritingFlags2.getStaticFieldRetarget());
+    assertEquals(rewritingFlags1.getCovariantRetarget(), rewritingFlags2.getCovariantRetarget());
+    assertEquals(rewritingFlags1.getStaticRetarget(), rewritingFlags2.getStaticRetarget());
+    assertEquals(
+        rewritingFlags1.getNonEmulatedVirtualRetarget(),
+        rewritingFlags2.getNonEmulatedVirtualRetarget());
+    assertEquals(
+        rewritingFlags1.getEmulatedVirtualRetarget(), rewritingFlags2.getEmulatedVirtualRetarget());
+    assertEquals(
+        rewritingFlags1.getEmulatedVirtualRetargetThroughEmulatedInterface(),
+        rewritingFlags2.getEmulatedVirtualRetargetThroughEmulatedInterface());
+    assertEquals(
+        rewritingFlags1.getApiGenericConversion().keySet(),
+        rewritingFlags2.getApiGenericConversion().keySet());
+    rewritingFlags1
+        .getApiGenericConversion()
+        .keySet()
+        .forEach(
+            k ->
+                assertArrayEquals(
+                    rewritingFlags1.getApiGenericConversion().get(k),
+                    rewritingFlags2.getApiGenericConversion().get(k)));
+    assertEquals(
+        rewritingFlags1.getEmulatedInterfaces().keySet(),
+        rewritingFlags2.getEmulatedInterfaces().keySet());
+    assertEquals(rewritingFlags1.getWrappers().keySet(), rewritingFlags2.getWrappers().keySet());
+    assertEquals(rewritingFlags1.getLegacyBackport(), rewritingFlags2.getLegacyBackport());
+    assertEquals(rewritingFlags1.getDontRetarget(), rewritingFlags2.getDontRetarget());
+    assertEquals(rewritingFlags1.getCustomConversions(), rewritingFlags2.getCustomConversions());
+    assertEquals(rewritingFlags1.getAmendLibraryMethod(), rewritingFlags2.getAmendLibraryMethod());
+    assertEquals(rewritingFlags1.getAmendLibraryField(), rewritingFlags2.getAmendLibraryField());
+  }
+
+  private void assertFlagsEquals(
       HumanRewritingFlags humanRewritingFlags1, HumanRewritingFlags humanRewritingFlags2) {
     assertEquals(humanRewritingFlags1.getRewritePrefix(), humanRewritingFlags2.getRewritePrefix());
     assertEquals(
@@ -123,6 +247,20 @@
   }
 
   private void assertTopLevelFlagsEquals(
+      MachineTopLevelFlags topLevelFlags1, MachineTopLevelFlags topLevelFlags2) {
+    String kr1 = String.join("\n", topLevelFlags1.getExtraKeepRules());
+    String kr2 = String.join("\n", topLevelFlags2.getExtraKeepRules());
+    assertEquals(kr1, kr2);
+    assertEquals(topLevelFlags1.getIdentifier(), topLevelFlags2.getIdentifier());
+    assertEquals(
+        topLevelFlags1.getRequiredCompilationApiLevel().getLevel(),
+        topLevelFlags2.getRequiredCompilationApiLevel().getLevel());
+    assertEquals(
+        topLevelFlags1.getSynthesizedLibraryClassesPackagePrefix(),
+        topLevelFlags2.getSynthesizedLibraryClassesPackagePrefix());
+  }
+
+  private void assertTopLevelFlagsEquals(
       HumanTopLevelFlags topLevelFlags1, HumanTopLevelFlags topLevelFlags2) {
     assertEquals(topLevelFlags1.getExtraKeepRules(), topLevelFlags2.getExtraKeepRules());
     assertEquals(topLevelFlags1.getIdentifier(), topLevelFlags2.getIdentifier());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index 70fbd07..8b8118f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
@@ -14,13 +14,21 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.DexApplication;
 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.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
 
 public class LibraryDesugaringSpecification {
 
@@ -258,6 +266,27 @@
     return ImmutableList.of(JDK8, JDK11);
   }
 
+  public DexApplication getAppForTesting(InternalOptions options, boolean libraryCompilation)
+      throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    if (libraryCompilation) {
+      builder.addProgramFiles(getDesugarJdkLibs());
+    }
+    AndroidApp inputApp = builder.addLibraryFiles(getLibraryFiles()).build();
+    return internalReadApp(inputApp, options);
+  }
+
+  private DexApplication internalReadApp(AndroidApp inputApp, InternalOptions options)
+      throws IOException {
+    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;
+  }
+
   public boolean hasEmulatedInterfaceDesugaring(TestParameters parameters) {
     return parameters.getApiLevel().getLevel() < descriptor.getEmulatedInterfaceDesugaring();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
deleted file mode 100644
index cb549f5..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.nestaccesscontrol;
-
-import static junit.framework.TestCase.assertTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-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 Java11R8CompilationTest extends TestBase {
-
-  public Java11R8CompilationTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        // Use of APIs, such as java.nio.file.* are only available from 26+.
-        .withApiLevelsStartingAtIncluding(AndroidApiLevel.O)
-        .withDexRuntimes()
-        .build();
-  }
-
-  private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
-
-  private static void assertNoNests(CodeInspector inspector) {
-    assertTrue(
-        inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
-  }
-
-  @Test
-  public void testR8CompiledWithR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .setMinApi(parameters.getApiLevel())
-        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
-        .addKeepRuleFiles(MAIN_KEEP)
-        .compile()
-        .inspect(this::assertNotEmpty)
-        .inspect(Java11R8CompilationTest::assertNoNests);
-  }
-
-  private void assertNotEmpty(CodeInspector inspector) {
-    assertTrue(inspector.allClasses().size() > 0);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java11R8BootstrapTest.java
similarity index 75%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
rename to src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java11R8BootstrapTest.java
index 04e2842..b3a9adf 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java11R8BootstrapTest.java
@@ -1,12 +1,12 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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.desugar.nestaccesscontrol;
+package com.android.tools.r8.desugar.r8bootstrap;
 
+import static com.android.tools.r8.desugar.r8bootstrap.JavaBootstrapUtils.MAIN_KEEP;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
 
 import com.android.tools.r8.Jdk11TestUtils;
 import com.android.tools.r8.TestBase;
@@ -15,8 +15,6 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
-import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -37,7 +35,6 @@
 @RunWith(Parameterized.class)
 public class Java11R8BootstrapTest extends TestBase {
 
-  private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
   private static final String[] HELLO_KEEP = {
     "-keep class hello.Hello {  public static void main(...);}"
   };
@@ -63,31 +60,10 @@
   }
 
   private static Path compileR8(boolean desugar) throws Exception {
-    // Shrink R8 11 with R8
-    return testForR8(TestBase.getStaticTemp(), Backend.CF)
-        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
-        .addLibraryFiles(Jdk11TestUtils.getJdk11LibraryFiles(getStaticTemp()))
-        .addKeepRuleFiles(MAIN_KEEP)
-        .applyIf(
-            desugar,
-            builder ->
-                builder.addOptionsModification(
-                    options -> {
-                      options.desugarState = DesugarState.ON;
-                    }))
-        .compile()
-        .inspect(inspector -> assertNests(inspector, desugar))
-        .writeToZip();
-  }
-
-  private static void assertNests(CodeInspector inspector, boolean desugar) {
-    if (desugar) {
-      assertTrue(
-          inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
-    } else {
-      assertTrue(
-          inspector.allClasses().stream().anyMatch(subj -> subj.getDexProgramClass().isInANest()));
-    }
+    return JavaBootstrapUtils.compileR8(
+        ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR,
+        Jdk11TestUtils.getJdk11LibraryFiles(getStaticTemp()),
+        desugar);
   }
 
   private Path[] jarsToCompare() {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
similarity index 65%
copy from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
copy to src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
index 04e2842..2ed69e1 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
@@ -1,12 +1,12 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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.desugar.nestaccesscontrol;
+package com.android.tools.r8.desugar.r8bootstrap;
 
+import static com.android.tools.r8.desugar.r8bootstrap.JavaBootstrapUtils.MAIN_KEEP;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
 
 import com.android.tools.r8.Jdk11TestUtils;
 import com.android.tools.r8.TestBase;
@@ -15,8 +15,6 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
-import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -28,24 +26,23 @@
 import org.junit.runners.Parameterized.Parameters;
 
 /**
- * This test relies on a freshly built build/libs/r8_with_relocated_deps_11.jar.
+ * This test relies on a freshly built build/libs/r8_with_relocated_deps_17.jar.
  *
- * <p>The test compiles Hello/R8 with the same settings using R8 compiled with Java 11 (non shrunk),
+ * <p>The test compiles Hello/R8 with the same settings using R8 compiled with Java 17 (non shrunk),
  * and the same but shrunk with/without nest desugaring. All generated jars should be run correctly
  * and identical.
  */
 @RunWith(Parameterized.class)
-public class Java11R8BootstrapTest extends TestBase {
+public class Java17R8BootstrapTest extends TestBase {
 
-  private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
   private static final String[] HELLO_KEEP = {
     "-keep class hello.Hello {  public static void main(...);}"
   };
 
-  private static Path r8Lib11NoDesugar;
-  private static Path r8Lib11Desugar;
+  private static Path r8Lib17NoDesugar;
+  private static Path r8Lib17Desugar;
 
-  public Java11R8BootstrapTest(TestParameters parameters) {
+  public Java17R8BootstrapTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -53,54 +50,39 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build();
+    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK17).build();
+  }
+
+  private static boolean supportsSealedClassesWhenGeneratingCf() {
+    // TODO(b/227160052): When this is true enable this test.
+    return false;
   }
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    r8Lib11NoDesugar = compileR8(false);
-    r8Lib11Desugar = compileR8(true);
+    if (!supportsSealedClassesWhenGeneratingCf()) {
+      return;
+    }
+    r8Lib17NoDesugar = compileR8(false);
+    r8Lib17Desugar = compileR8(true);
   }
 
   private static Path compileR8(boolean desugar) throws Exception {
-    // Shrink R8 11 with R8
-    return testForR8(TestBase.getStaticTemp(), Backend.CF)
-        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
-        .addLibraryFiles(Jdk11TestUtils.getJdk11LibraryFiles(getStaticTemp()))
-        .addKeepRuleFiles(MAIN_KEEP)
-        .applyIf(
-            desugar,
-            builder ->
-                builder.addOptionsModification(
-                    options -> {
-                      options.desugarState = DesugarState.ON;
-                    }))
-        .compile()
-        .inspect(inspector -> assertNests(inspector, desugar))
-        .writeToZip();
-  }
-
-  private static void assertNests(CodeInspector inspector, boolean desugar) {
-    if (desugar) {
-      assertTrue(
-          inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
-    } else {
-      assertTrue(
-          inspector.allClasses().stream().anyMatch(subj -> subj.getDexProgramClass().isInANest()));
-    }
+    return JavaBootstrapUtils.compileR8(
+        ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR,
+        Jdk11TestUtils.getJdk11LibraryFiles(getStaticTemp()),
+        desugar);
   }
 
   private Path[] jarsToCompare() {
-    return new Path[] {
-      ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR,
-      r8Lib11NoDesugar,
-      r8Lib11Desugar
-    };
+    return new Path[] {ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR, r8Lib17NoDesugar, r8Lib17Desugar};
   }
 
   @Test
   public void testHello() throws Exception {
     Assume.assumeTrue(!ToolHelper.isWindows());
+    Assume.assumeTrue(JavaBootstrapUtils.exists(ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR));
+    Assume.assumeTrue(supportsSealedClassesWhenGeneratingCf());
     Path prevGeneratedJar = null;
     String prevRunResult = null;
     for (Path jar : jarsToCompare()) {
@@ -131,7 +113,9 @@
   public void testR8() throws Exception {
     Assume.assumeTrue(!ToolHelper.isWindows());
     Assume.assumeTrue(parameters.isCfRuntime());
-    Assume.assumeTrue(CfVm.JDK11.lessThanOrEqual(parameters.getRuntime().asCf().getVm()));
+    Assume.assumeTrue(CfVm.JDK17.lessThanOrEqual(parameters.getRuntime().asCf().getVm()));
+    Assume.assumeTrue(JavaBootstrapUtils.exists(ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR));
+    Assume.assumeTrue(supportsSealedClassesWhenGeneratingCf());
     Path prevGeneratedJar = null;
     for (Path jar : jarsToCompare()) {
       Path generatedJar =
diff --git a/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaBootstrapUtils.java b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaBootstrapUtils.java
new file mode 100644
index 0000000..63d7dde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaBootstrapUtils.java
@@ -0,0 +1,57 @@
+// 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.desugar.r8bootstrap;
+
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class JavaBootstrapUtils extends TestBase {
+
+  static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+
+  static boolean exists(Path r8WithRelocatedDeps) {
+    // This test runs only if the dependencies have been generated using:
+    // <code> tools/gradle.py r8WithRelocatedDeps17 r8WithRelocatedDeps11 </code>
+    return Files.exists(r8WithRelocatedDeps);
+  }
+
+  static Path compileR8(Path r8WithRelocatedDeps, Path[] libraryFiles, boolean desugar)
+      throws Exception {
+    if (!exists(r8WithRelocatedDeps)) {
+      return null;
+    }
+    // Shrink R8 11 with R8
+    return testForR8(getStaticTemp(), Backend.CF)
+        .addProgramFiles(r8WithRelocatedDeps)
+        .addLibraryFiles(libraryFiles)
+        .addKeepRuleFiles(MAIN_KEEP)
+        .applyIf(
+            desugar,
+            builder ->
+                builder.addOptionsModification(
+                    options -> {
+                      options.desugarState = DesugarState.ON;
+                    }))
+        .compile()
+        .inspect(inspector -> assertNests(inspector, desugar))
+        .writeToZip();
+  }
+
+  private static void assertNests(CodeInspector inspector, boolean desugar) {
+    if (desugar) {
+      assertTrue(
+          inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
+    } else {
+      assertTrue(
+          inspector.allClasses().stream().anyMatch(subj -> subj.getDexProgramClass().isInANest()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaD8CompilationTest.java
similarity index 78%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
rename to src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaD8CompilationTest.java
index a4c0ca7..1970880 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaD8CompilationTest.java
@@ -1,8 +1,8 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// 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.desugar.nestaccesscontrol;
+package com.android.tools.r8.desugar.r8bootstrap;
 
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
@@ -12,7 +12,6 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.references.Reference;
@@ -20,9 +19,12 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Path;
+import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -32,15 +34,21 @@
 import org.objectweb.asm.ClassVisitor;
 
 @RunWith(Parameterized.class)
-public class Java11D8CompilationTest extends TestBase {
+public class JavaD8CompilationTest extends TestBase {
 
-  public Java11D8CompilationTest(TestParameters parameters) {
+  private final Path r8WithRelocatedDeps;
+
+  public JavaD8CompilationTest(TestParameters parameters, Path r8WithRelocatedDeps) {
     parameters.assertNoneRuntime();
+    this.r8WithRelocatedDeps = r8WithRelocatedDeps;
   }
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  @Parameters(name = "{0}, java: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(),
+        ImmutableList.of(
+            ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR, ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR));
   }
 
   private static void assertNoNests(CodeInspector inspector) {
@@ -50,22 +58,24 @@
 
   @Test
   public void testR8CompiledWithD8() throws Exception {
+    Assume.assumeTrue(JavaBootstrapUtils.exists(r8WithRelocatedDeps));
     testForD8()
-        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
+        .addProgramFiles(r8WithRelocatedDeps)
         .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
         .compile()
-        .inspect(Java11D8CompilationTest::assertNoNests);
+        .inspect(JavaD8CompilationTest::assertNoNests);
   }
 
   @Test
   public void testR8CompiledWithD8ToCf() throws Exception {
+    Assume.assumeTrue(JavaBootstrapUtils.exists(r8WithRelocatedDeps));
     Path r8Desugared =
         testForD8(Backend.CF)
-            .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
+            .addProgramFiles(r8WithRelocatedDeps)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMinApi(AndroidApiLevel.B)
             .compile()
-            .inspect(Java11D8CompilationTest::assertNoNests)
+            .inspect(JavaD8CompilationTest::assertNoNests)
             .writeToZip();
 
     // Check that the desugared classes has the expected class file versions and that no nest
diff --git a/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaR8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaR8CompilationTest.java
new file mode 100644
index 0000000..09cb049
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/JavaR8CompilationTest.java
@@ -0,0 +1,69 @@
+// 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.desugar.r8bootstrap;
+
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class JavaR8CompilationTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final Path r8WithRelocatedDeps;
+
+  public JavaR8CompilationTest(TestParameters parameters, Path r8WithRelocatedDeps) {
+    this.parameters = parameters;
+    this.r8WithRelocatedDeps = r8WithRelocatedDeps;
+  }
+
+  @Parameters(name = "{0}, java: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            // Use of APIs, such as java.nio.file.* are only available from 26+.
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.O)
+            .withDexRuntimes()
+            .build(),
+        ImmutableList.of(
+            ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR, ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR));
+  }
+
+  private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+
+  private static void assertNoNests(CodeInspector inspector) {
+    assertTrue(
+        inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
+  }
+
+  @Test
+  public void testR8CompiledWithR8() throws Exception {
+    Assume.assumeTrue(JavaBootstrapUtils.exists(r8WithRelocatedDeps));
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramFiles(r8WithRelocatedDeps)
+        .addKeepRuleFiles(MAIN_KEEP)
+        .compile()
+        .inspect(this::assertNotEmpty)
+        .inspect(JavaR8CompilationTest::assertNoNests);
+  }
+
+  private void assertNotEmpty(CodeInspector inspector) {
+    assertTrue(inspector.allClasses().size() > 0);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java
index 7c2ae96..6860b8b 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordClasspathTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
@@ -34,19 +35,35 @@
   private static final String EXPECTED_RESULT = StringUtils.lines("Hello");
 
   private final TestParameters parameters;
+  private final boolean stripClasspath;
 
-  public RecordClasspathTest(TestParameters parameters) {
+  public RecordClasspathTest(TestParameters parameters, boolean stripClasspath) {
     this.parameters = parameters;
+    this.stripClasspath = stripClasspath;
   }
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameterized.Parameters(name = "{0}, strip: {1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters()
             .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
-            .build());
+            .build(),
+        BooleanUtils.values());
+  }
+
+  private byte[][] getClasspathData() {
+    return stripClasspath ? stripFields(PROGRAM_DATA_1) : PROGRAM_DATA_1;
+  }
+
+  private byte[][] stripFields(byte[][] programData1) {
+    byte[][] bytes = new byte[programData1.length][];
+    for (int i = 0; i < programData1.length; i++) {
+      bytes[i] =
+          transformer(programData1[i], null).removeFields((a, b, c, d, e) -> true).transform();
+    }
+    return bytes;
   }
 
   @Test
@@ -54,13 +71,13 @@
     if (parameters.isCfRuntime()) {
       testForJvm()
           .addProgramClasses(TestClass.class)
-          .addClasspathClassFileData(PROGRAM_DATA_1)
+          .addClasspathClassFileData(getClasspathData())
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutput(EXPECTED_RESULT);
     }
     testForD8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
-        .addClasspathClassFileData(PROGRAM_DATA_1)
+        .addClasspathClassFileData(getClasspathData())
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::assertNoRecord)
@@ -74,7 +91,7 @@
     Assume.assumeFalse(parameters.isCfRuntime());
     testForD8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
-        .addClasspathClassFileData(PROGRAM_DATA_1)
+        .addClasspathClassFileData(getClasspathData())
         .setMinApi(parameters.getApiLevel())
         .setIntermediate(true)
         .setOutputMode(OutputMode.DexFilePerClassFile)
@@ -91,7 +108,7 @@
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
             .addProgramClasses(TestClass.class)
-            .addClasspathClassFileData(PROGRAM_DATA_1)
+            .addClasspathClassFileData(getClasspathData())
             .setMinApi(parameters.getApiLevel())
             .addKeepMainRule(TestClass.class);
     if (parameters.isCfRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/diagnostics/UnsupportedFeaturesDiagnosticsTest.java b/src/test/java/com/android/tools/r8/diagnostics/UnsupportedFeaturesDiagnosticsTest.java
index 8133dc2..1901f24 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/UnsupportedFeaturesDiagnosticsTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/UnsupportedFeaturesDiagnosticsTest.java
@@ -68,7 +68,7 @@
     parameters.assertNoneRuntime();
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testInvokeLambdaMetafactory() throws Exception {
     testForD8()
         .addProgramClassesAndInnerClasses(LambdaMetafactoryTest.class)
@@ -77,8 +77,8 @@
         .compileWithExpectedDiagnostics(
             diagnostics -> {
               diagnostics
-                  .assertOnlyErrors()
-                  .assertErrorsMatch(
+                  .assertOnlyWarnings()
+                  .assertWarningsMatch(
                       allOf(
                           matches("invoke-custom"),
                           diagnosticMessage(startsWith(AGP_INVOKE_CUSTOM))));
diff --git a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
index d420618..e7511bd 100644
--- a/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
+++ b/src/test/java/com/android/tools/r8/naming/SeedMapperTests.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
-import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -58,7 +57,7 @@
       assertEquals(
           String.format(ProguardMapError.DUPLICATE_SOURCE_MESSAGE, "A.B.C"),
           diagnostic.getDiagnosticMessage());
-      assertEquals(2, ((TextPosition) diagnostic.getPosition()).getLine());
+      assertEquals("line 2", diagnostic.getPosition().getDescription());
     }
   }
 
@@ -81,7 +80,7 @@
       assertEquals(
           String.format(ProguardMapError.DUPLICATE_SOURCE_MESSAGE, "int aaaa(B)"),
           diagnostic.getDiagnosticMessage());
-      assertEquals(3, ((TextPosition) diagnostic.getPosition()).getLine());
+      assertEquals("line 3", diagnostic.getPosition().getDescription());
     }
   }
 
@@ -104,7 +103,7 @@
       assertEquals(
           String.format(ProguardMapError.DUPLICATE_SOURCE_MESSAGE, "int aaaa"),
           diagnostic.getDiagnosticMessage());
-      assertEquals(3, ((TextPosition) diagnostic.getPosition()).getLine());
+      assertEquals("line 3", diagnostic.getPosition().getDescription());
     }
   }
 
@@ -122,7 +121,7 @@
       assertEquals(
           String.format(ProguardMapError.DUPLICATE_TARGET_MESSAGE, "A.B.D", "A.B.C", "a"),
           diagnostic.getDiagnosticMessage());
-      assertEquals(2, ((TextPosition) diagnostic.getPosition()).getLine());
+      assertEquals("line 2", diagnostic.getPosition().getDescription());
     }
   }
 
@@ -161,7 +160,7 @@
       assertEquals(
           String.format(ProguardMapError.DUPLICATE_TARGET_MESSAGE, "int bar(A)", "int foo(A)", "a"),
           diagnostic.getDiagnosticMessage());
-      assertEquals(2, ((TextPosition) diagnostic.getPosition()).getLine());
+      assertEquals("line 2", diagnostic.getPosition().getDescription());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
index e891067..08516ec 100644
--- a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
@@ -56,6 +56,7 @@
   }
 
   public RelocatorTest(TestParameters parameters, boolean external) {
+    parameters.assertNoneRuntime();
     this.external = external;
   }
 
@@ -275,15 +276,15 @@
   }
 
   @Test
-  public void testNest() throws IOException, CompilationFailedException, ExecutionException {
+  public void testNest() throws IOException, CompilationFailedException {
     String originalPrefix = "com.android.tools.r8";
     String newPrefix = "com.android.tools.r8";
     Path output = temp.newFile("output.jar").toPath();
     Map<String, String> mapping = new HashMap<>();
     mapping.put(originalPrefix, newPrefix);
-    runRelocator(ToolHelper.R8_WITH_DEPS_11_JAR, mapping, output);
+    runRelocator(ToolHelper.R8_WITH_DEPS_17_JAR, mapping, output);
     // Assert that all classes are the same, have the same methods and nest info.
-    CodeInspector originalInspector = new CodeInspector(ToolHelper.R8_WITH_DEPS_11_JAR);
+    CodeInspector originalInspector = new CodeInspector(ToolHelper.R8_WITH_DEPS_17_JAR);
     CodeInspector relocatedInspector = new CodeInspector(output);
     for (FoundClassSubject originalSubject : originalInspector.allClasses()) {
       ClassSubject relocatedSubject = relocatedInspector.clazz(originalSubject.getFinalName());
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java
new file mode 100644
index 0000000..ce8088f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java
@@ -0,0 +1,77 @@
+// 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.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageClassWithKeepPackageNameOnTargetTest extends RepackageTestBase {
+
+  private static final String DESTINATION_PACKAGE = "other.package";
+
+  public RepackageClassWithKeepPackageNameOnTargetTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Override
+  protected String getRepackagePackage() {
+    return DESTINATION_PACKAGE;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/241220445): Should be able to relocate when having -dontobfuscate in some way to
+    //  support mainline.
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .apply(this::configureRepackaging)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo::foo()")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(Foo.class);
+              assertThat(clazz, isPresent());
+              assertThat(clazz.getFinalName(), startsWith(DESTINATION_PACKAGE));
+              String relocatedPackageSuffix =
+                  DescriptorUtils.getPackageBinaryNameFromJavaType(
+                      clazz.getFinalName().substring(DESTINATION_PACKAGE.length() + 1));
+              String originalPackage =
+                  DescriptorUtils.getPackageBinaryNameFromJavaType(clazz.getOriginalName());
+              // TODO(b/241220445): Have a configuration where the suffix is identicial to the
+              // original.
+              assertNotEquals(relocatedPackageSuffix, originalPackage);
+            });
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Foo.foo();
+    }
+  }
+
+  public static class Foo {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("Foo::foo()");
+    }
+  }
+}
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 4602712..c62aa08 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -508,6 +508,7 @@
                   ProguardMappingSupplier.builder()
                       .setProguardMapProducer(
                           ProguardMapProducer.fromString(stackTraceForTest.mapping()))
+                      .setLoadAllDefinitions(!stream)
                       .setAllowExperimental(allowExperimentalMapping)
                       .build())
               .setRetracedStackTraceConsumer(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java
index 5563fea..08d73db 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInferSourceFileTest.java
@@ -33,7 +33,7 @@
 
   public static class ApiTest implements RetraceApiBinaryTest {
 
-    private final String mapping =
+    private static final String mapping =
         "some.Class -> a:\n"
             + "  1:3:int strawberry(int):99:101 -> s\n"
             + "  4:5:int mango(float):121:122 -> s";
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
index 8a86283..793a22d 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
@@ -62,11 +63,11 @@
 
     @Test
     public void test() {
-      ProguardMappingSupplier mappingProvider =
+      ProguardMappingSupplier mappingSupplier =
           ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
               .build();
-      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
+      Retracer retracer = mappingSupplier.createRetracer(new DiagnosticsHandler() {});
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
index cfefe61..c037782 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
@@ -70,11 +71,11 @@
 
     @Test
     public void test() {
-      ProguardMappingSupplier mappingProvider =
+      ProguardMappingSupplier mappingSupplier =
           ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
               .build();
-      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
+      Retracer retracer = mappingSupplier.createRetracer(new DiagnosticsHandler() {});
       // Retrace the first outline.
       RetraceStackTraceContext outlineContext =
           retraceOutline(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
index 34f1217..0479f47 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
@@ -65,7 +66,7 @@
           ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
               .build();
-      Retracer retracer = Retracer.builder().setMappingSupplier(mappingSupplier).build();
+      Retracer retracer = mappingSupplier.createRetracer(new DiagnosticsHandler() {});
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
index 38736ce..93611eb 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
@@ -61,11 +62,11 @@
 
     @Test
     public void test() {
-      ProguardMappingSupplier mappingProvider =
+      ProguardMappingSupplier mappingSupplier =
           ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
               .build();
-      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
+      Retracer retracer = mappingSupplier.createRetracer(new DiagnosticsHandler() {});
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
index 76b0ff4..de43fae 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
@@ -79,11 +80,11 @@
 
     @Test
     public void testUsingObfuscatedName() {
-      ProguardMappingSupplier mappingProvider =
+      ProguardMappingSupplier mappingSupplier =
           ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
               .build();
-      Retracer retracer = Retracer.builder().setMappingSupplier(mappingProvider).build();
+      Retracer retracer = mappingSupplier.createRetracer(new DiagnosticsHandler() {});
       List<RetraceThrownExceptionElement> npeRetraced =
           retracer.retraceThrownException(renamedException).stream().collect(Collectors.toList());
       assertEquals(1, npeRetraced.size());
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
index 4334060..b244742 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
@@ -59,15 +59,11 @@
     @Test
     public void testFirstStackLineIsRemoved() {
       TestDiagnosticsHandler testDiagnosticsHandler = new TestDiagnosticsHandler();
-      ProguardMappingSupplier mappingProvider =
+      ProguardMappingSupplier mappingSupplier =
           ProguardMappingSupplier.builder()
               .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
               .build();
-      Retracer retracer =
-          Retracer.builder()
-              .setMappingSupplier(mappingProvider)
-              .setDiagnosticsHandler(testDiagnosticsHandler)
-              .build();
+      Retracer retracer = mappingSupplier.createRetracer(testDiagnosticsHandler);
 
       List<RetraceThrownExceptionElement> npeRetraced =
           retracer.retraceThrownException(Reference.classFromDescriptor(npeDescriptor)).stream()
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripInlineTest.java
index 3bc2eb2..5d78d68 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripInlineTest.java
@@ -66,9 +66,10 @@
     @Test
     public void test() throws IOException {
       ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping);
+      DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
       Map<String, byte[]> partitions = new HashMap<>();
       MappingPartitionMetadata metadataData =
-          ProguardMapPartitioner.builder(new DiagnosticsHandler() {})
+          ProguardMapPartitioner.builder(diagnosticsHandler)
               .setProguardMapProducer(proguardMapProducer)
               .setPartitionConsumer(
                   partition -> partitions.put(partition.getKey(), partition.getPayload()))
@@ -91,7 +92,8 @@
                   })
               .build();
       assertEquals(0, prepareCounter);
-      Retracer retracer = Retracer.builder().setMappingSupplier(mappingSupplier).build();
+      mappingSupplier.registerClassUse(diagnosticsHandler, callerRenamed);
+      Retracer retracer = mappingSupplier.createRetracer(diagnosticsHandler);
       List<RetraceFrameElement> callerRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java
index 0eac918..732c1ce 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionRoundTripTest.java
@@ -75,8 +75,9 @@
     public void test() throws IOException {
       ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping);
       Map<String, byte[]> partitions = new HashMap<>();
+      DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
       MappingPartitionMetadata metadataData =
-          ProguardMapPartitioner.builder(new DiagnosticsHandler() {})
+          ProguardMapPartitioner.builder(diagnosticsHandler)
               .setProguardMapProducer(proguardMapProducer)
               .setPartitionConsumer(
                   partition -> partitions.put(partition.getKey(), partition.getPayload()))
@@ -98,7 +99,8 @@
                   })
               .build();
       assertEquals(0, prepareCounter);
-      Retracer retracer = Retracer.builder().setMappingSupplier(mappingSupplier).build();
+      mappingSupplier.registerClassUse(diagnosticsHandler, outlineRenamed);
+      Retracer retracer = mappingSupplier.createRetracer(diagnosticsHandler);
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
@@ -130,6 +132,8 @@
       assertEquals(0, rewrittenReferences.size());
 
       // Retrace the outline position
+      mappingSupplier.registerClassUse(diagnosticsHandler, callsiteRenamed);
+      retracer = mappingSupplier.createRetracer(diagnosticsHandler);
       RetraceStackTraceContext context = retraceFrameElement.getRetraceStackTraceContext();
       List<RetraceFrameElement> retraceOutlineCallee =
           retracer
diff --git a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
index 6047b63..07f4487 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.references.ClassReference;
@@ -74,17 +75,17 @@
     return ImmutableList.of("foo");
   }
 
-  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
       throws NoSuchMethodException {
     return ImmutableList.of(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(Main.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(AStartupClass.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(AStartupClass.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
index 5e25d36..7ae582f 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -13,6 +13,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -96,25 +97,25 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
       throws NoSuchMethodException {
-    ImmutableList.Builder<StartupMethod<ClassReference, MethodReference>> builder =
+    ImmutableList.Builder<StartupItem<ClassReference, MethodReference, ?>> builder =
         ImmutableList.builder();
     builder.add(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(Main.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(A.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(A.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(B.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(
@@ -122,21 +123,8 @@
             .build());
     if (useLambda) {
       builder.add(
-          StartupMethod.referenceBuilder()
-              .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
-              .setSynthetic()
-              .build(),
-          StartupMethod.referenceBuilder()
-              .setMethodReference(MethodReferenceUtils.instanceConstructor(B.class))
-              .setSynthetic()
-              .build(),
-          StartupMethod.referenceBuilder()
-              .setMethodReference(
-                  Reference.method(
-                      Reference.classFromClass(B.class),
-                      "accept",
-                      ImmutableList.of(Reference.classFromClass(Object.class)),
-                      null))
+          StartupClass.referenceBuilder()
+              .setClassReference(Reference.classFromClass(B.class))
               .setSynthetic()
               .build(),
           StartupMethod.referenceBuilder()
@@ -145,8 +133,8 @@
               .build());
     }
     builder.add(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(C.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(C.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(C.class.getDeclaredMethod("c")))
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
index e1e3149..3087653 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
@@ -14,6 +14,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupItem;
 import com.android.tools.r8.experimental.startup.StartupMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -30,7 +31,6 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -93,46 +93,36 @@
     return ImmutableList.of("A", "B", "C");
   }
 
-  private List<StartupMethod<ClassReference, MethodReference>> getExpectedStartupList()
+  private List<StartupItem<ClassReference, MethodReference, ?>> getExpectedStartupList()
       throws NoSuchMethodException {
     return ImmutableList.of(
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(Main.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(Main.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(A.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(A.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(A.class.getDeclaredMethod("a")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(B.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("b")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(B.class))
-            .setSynthetic()
-            .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.instanceConstructor(B.class))
-            .setSynthetic()
-            .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(
-                Reference.method(
-                    Reference.classFromClass(B.class), "run", Collections.emptyList(), null))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(B.class))
             .setSynthetic()
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(B.class.getDeclaredMethod("lambda$b$0")))
             .build(),
-        StartupMethod.referenceBuilder()
-            .setMethodReference(MethodReferenceUtils.classConstructor(C.class))
+        StartupClass.referenceBuilder()
+            .setClassReference(Reference.classFromClass(C.class))
             .build(),
         StartupMethod.referenceBuilder()
             .setMethodReference(Reference.methodFromMethod(C.class.getDeclaredMethod("c")))
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 74b5de5..e29b4e8 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
@@ -36,7 +36,8 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.Retracer;
-import com.android.tools.r8.retrace.internal.ProguardMappingSupplierImpl;
+import com.android.tools.r8.retrace.internal.MappingSupplierInternalImpl;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
@@ -184,10 +185,9 @@
   public Retracer getRetracer() {
     if (lazyRetracer == null) {
       lazyRetracer =
-          Retracer.builder()
-              .setMappingSupplier(new ProguardMappingSupplierImpl(mapping))
-              .setDiagnosticsHandler(new TestDiagnosticMessagesImpl())
-              .build();
+          RetracerImpl.createInternal(
+              MappingSupplierInternalImpl.createInternal(mapping),
+              new TestDiagnosticMessagesImpl());
     }
     return lazyRetracer;
   }
@@ -548,11 +548,9 @@
   }
 
   public Retracer retrace() {
-    return Retracer.builder()
-        .setMappingSupplier(
-            new ProguardMappingSupplierImpl(
-                mapping == null ? ClassNameMapper.builder().build() : mapping))
-        .setDiagnosticsHandler(new TestDiagnosticMessagesImpl())
-        .build();
+    return RetracerImpl.createInternal(
+        MappingSupplierInternalImpl.createInternal(
+            mapping == null ? ClassNameMapper.builder().build() : mapping),
+        new TestDiagnosticMessagesImpl());
   }
 }
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index da20d2e..750079a 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-4408d86adfb21f32c2dcb93c2c8f2e392f0e0de0
\ No newline at end of file
+ed632240a9f5652ed988ccf84c5f592a487d24c3
\ No newline at end of file
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index de9f0ac..33e6f75 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -372,7 +372,7 @@
         jdk.GetJavaExecutable(),
         '-cp',
         utils.R8_JAR,
-        'com.android.tools.r8.GenerateLintFiles',
+        'com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateLintFiles',
         configuration,
         implementation,
         lint_dir]
diff --git a/tools/r8_release.py b/tools/r8_release.py
index a25baf6..c00c15c 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -366,13 +366,18 @@
       g4_open('src.jar')
       g4_open('lib.jar')
       g4_open('lib.jar.map')
+      g4_open('retrace_full.jar')
       g4_open('retrace_lib.jar')
+      g4_open('retrace_lib.jar.map')
       g4_open('desugar_jdk_libs_configuration.jar')
       download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar')
+      download_file(options.version, 'r8-full-exclude-deps.jar', 'retrace_full.jar')
       download_file(options.version, 'r8-src.jar', 'src.jar')
       download_file(options.version, 'r8lib-exclude-deps.jar', 'lib.jar')
       download_file(
           options.version, 'r8lib-exclude-deps.jar.map', 'lib.jar.map')
+      download_file(
+          options.version, 'r8lib-exclude-deps.jar.map', 'retrace_lib.jar.map')
       download_file(options.version, 'desugar_jdk_libs_configuration.jar',
                     'desugar_jdk_libs_configuration.jar')
       download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace_lib.jar')
@@ -418,6 +423,67 @@
   return release_google3
 
 
+def prepare_google3_retrace(args):
+  assert args.version
+  # Check if an existing client exists.
+  if not args.use_existing_work_branch:
+    check_no_google3_client(args, args.p4_client)
+
+  def release_google3_retrace(options):
+    print("Releasing Retrace for Google 3")
+    if options.dry_run:
+      return 'DryRun: omitting g3 release for %s' % options.version
+
+    google3_base = subprocess.check_output(
+      ['p4', 'g4d', '-f', args.p4_client]).decode('utf-8').rstrip()
+    third_party_r8 = os.path.join(google3_base, 'third_party', 'java', 'r8')
+    with utils.ChangedWorkingDirectory(third_party_r8):
+      # download files
+      g4_open('retrace_full.jar')
+      g4_open('retrace_lib.jar')
+      g4_open('retrace_lib.jar.map')
+      download_file(options.version, 'r8-full-exclude-deps.jar', 'retrace_full.jar')
+      download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace_lib.jar')
+      download_file(
+        options.version, 'r8lib-exclude-deps.jar.map', 'retrace_lib.jar.map')
+      g4_open('METADATA')
+      metadata_path = os.path.join(third_party_r8, 'METADATA')
+      match_count = 0
+      version_match_regexp = r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev/r8retrace-exclude-deps.jar'
+      for line in open(metadata_path, 'r'):
+        result = re.search(version_match_regexp, line)
+        if result:
+          match_count = match_count + 1
+      if match_count != 1:
+        print(("Could not find the previous retrace release string to replace in " +
+               "METADATA. Expected to find is mentioned 1 times. Please update %s " +
+               "manually and run again with options --google3retrace " +
+               "--use-existing-work-branch.") % metadata_path)
+        sys.exit(1)
+      sed(version_match_regexp, options.version + "/r8retrace-exclude-deps.jar", metadata_path)
+      subprocess.check_output('chmod u+w *', shell=True)
+
+    with utils.ChangedWorkingDirectory(google3_base):
+      blaze_result = blaze_run('//third_party/java/r8:retrace -- --version')
+
+      print(blaze_result)
+      assert options.version in blaze_result
+
+      if not options.no_upload:
+        change_result = g4_change(options.version)
+        change_result += 'Run \'(g4d ' + args.p4_client \
+                         + ' && tap_presubmit -p all --train -c ' \
+                         + get_cl_id(change_result) + ')\' for running TAP global' \
+                         + ' presubmit using the train.\n' \
+                         + 'Run \'(g4d ' + args.p4_client \
+                         + ' && tap_presubmit -p all --notrain --detach --email' \
+                         + ' --skip_flaky_targets --skip_already_failing -c ' \
+                         + get_cl_id(change_result) + ')\' for running an isolated' \
+                         + ' TAP global presubmit.'
+        return change_result
+
+  return release_google3_retrace
+
 def update_desugar_library_in_studio(args):
   assert os.path.exists(args.studio), ("Could not find STUDIO path %s"
                                        % args.studio)
@@ -843,6 +909,10 @@
                       default=False,
                       action='store_true',
                       help='Release for google 3')
+  result.add_argument('--google3retrace',
+                      default=False,
+                      action='store_true',
+                      help='Release retrace for google 3')
   result.add_argument('--p4-client',
                       default='update-r8',
                       metavar=('<client name>'),
@@ -906,6 +976,8 @@
 
   if args.google3:
     targets_to_run.append(prepare_google3(args))
+  if args.google3retrace:
+    targets_to_run.append(prepare_google3_retrace(args))
   if args.studio and not args.update_desugar_library_in_studio:
     targets_to_run.append(prepare_studio(args))
   if args.aosp:
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index f1faee43..26846bc 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -15,10 +15,12 @@
       generate_startup_profile(options)
   if options.logcat:
     write_tmp_logcat(logcat, iteration, options)
-    current_startup_descriptors = get_r8_startup_descriptors_from_logcat(logcat)
+    current_startup_descriptors = get_r8_startup_descriptors_from_logcat(
+        logcat, options)
   else:
     write_tmp_profile(profile, iteration, options)
-    write_tmp_profile_classes_and_methods(profile_classes_and_methods, iteration, options)
+    write_tmp_profile_classes_and_methods(
+        profile_classes_and_methods, iteration, options)
     current_startup_descriptors = \
         transform_classes_and_methods_to_r8_startup_descriptors(
             profile_classes_and_methods, options)
@@ -52,7 +54,7 @@
       # Clear logcat and start capturing logcat.
       adb_utils.clear_logcat(options.device_id)
       logcat_process = adb_utils.start_logcat(
-          options.device_id, format='raw', filter='r8:I *:S')
+          options.device_id, format='tag', filter='r8:I ActivityTaskManager:I *:S')
     else:
       # Clear existing profile data.
       adb_utils.clear_profile_data(options.app_id, options.device_id)
@@ -77,30 +79,96 @@
 
     # Shutdown app.
     adb_utils.stop_app(options.app_id, options.device_id)
-    adb_utils.tear_down_after_interaction_with_device(
+    adb_utils.teardown_after_interaction_with_device(
         tear_down_options, options.device_id)
 
   return (logcat, profile, profile_classes_and_methods)
 
-def get_r8_startup_descriptors_from_logcat(logcat):
-  startup_descriptors = []
+def get_r8_startup_descriptors_from_logcat(logcat, options):
+  post_startup = False
+  startup_descriptors = {}
   for line in logcat:
-    if line == '--------- beginning of main':
+    line_elements = parse_logcat_line(line)
+    if line_elements is None:
       continue
-    if line == '--------- beginning of system':
-      continue
-    if not line.startswith('L') or not line.endswith(';'):
-      print('Unrecognized line in logcat: %s' % line)
-      continue
-    startup_descriptors.append(line)
+    (priority, tag, message) = line_elements
+    if tag == 'ActivityTaskManager':
+      if message.startswith('START') \
+          or message.startswith('Activity pause timeout for') \
+          or message.startswith('Activity top resumed state loss timeout for') \
+          or message.startswith('Force removing')
+          or message.startswith(
+              'Launch timeout has expired, giving up wake lock!'):
+        continue
+      elif message.startswith('Displayed %s/' % options.app_id):
+        print('Entering post startup: %s' % message)
+        post_startup = True
+        continue
+    elif tag == 'r8':
+      if is_startup_descriptor(message):
+        startup_descriptors[message] = {
+          'conditional_startup': False,
+          'post_startup': post_startup
+        }
+        continue
+    # Reaching here means we didn't expect this line.
+    report_unrecognized_logcat_line(line)
   return startup_descriptors
 
+def is_startup_descriptor(string):
+  # The descriptor should start with the holder (possibly prefixed with 'S').
+  if not any(string.startswith('%sL' % flags) for flags in ['', 'S']):
+    return False
+  # The descriptor should end with ';', a primitive type, or void.
+  if not string.endswith(';') \
+      and not any(string.endswith(c) for c in get_primitive_descriptors()) \
+      and not string.endswith('V'):
+    return False
+  return True
+
+def get_primitive_descriptors():
+  return ['Z', 'B', 'S', 'C', 'I', 'F', 'J', 'D']
+
+def parse_logcat_line(line):
+  if line == '--------- beginning of kernel':
+    return None
+  if line == '--------- beginning of main':
+    return None
+  if line == '--------- beginning of system':
+    return None
+
+  priority = None
+  tag = None
+
+  try:
+    priority_end = line.index('/')
+    priority = line[0:priority_end]
+    line = line[priority_end + 1:]
+  except ValueError:
+    return report_unrecognized_logcat_line(line)
+
+  try:
+    tag_end = line.index(':')
+    tag = line[0:tag_end].strip()
+    line = line[tag_end + 1 :]
+  except ValueError:
+    return report_unrecognized_logcat_line(line)
+
+  message = line.strip()
+  return (priority, tag, message)
+
+def report_unrecognized_logcat_line(line):
+  print('Unrecognized line in logcat: %s' % line)
+
 def transform_classes_and_methods_to_r8_startup_descriptors(
     classes_and_methods, options):
   startup_descriptors = []
   for class_or_method in classes_and_methods:
     descriptor = class_or_method.get('descriptor')
     flags = class_or_method.get('flags')
+    if flags.get('conditional_startup') \
+        and not options.include_conditional_startup:
+      continue
     if flags.get('post_startup') \
         and not flags.get('startup') \
         and not options.include_post_startup:
@@ -110,7 +178,22 @@
 
 def add_r8_startup_descriptors(startup_descriptors, startup_descriptors_to_add):
   previous_number_of_startup_descriptors = len(startup_descriptors)
-  startup_descriptors.update(startup_descriptors_to_add)
+  if previous_number_of_startup_descriptors == 0:
+    for startup_descriptor, flags in startup_descriptors_to_add.items():
+      startup_descriptors[startup_descriptor] = flags.copy()
+  else:
+    for startup_descriptor, flags in startup_descriptors_to_add.items():
+      if startup_descriptor in startup_descriptors:
+        # Merge flags.
+        existing_flags = startup_descriptors[startup_descriptor]
+        assert not flags['conditional_startup']
+        if flags['post_startup']:
+          existing_flags['post_startup'] = True
+      else:
+        # Add new startup descriptor.
+        new_flags = flags.copy()
+        new_flags['conditional_startup'] = True
+        startup_descriptors[startup_descriptor] = new_flags
   new_number_of_startup_descriptors = len(startup_descriptors)
   return new_number_of_startup_descriptors \
       - previous_number_of_startup_descriptors
@@ -159,8 +242,20 @@
       item_to_string)
 
 def write_tmp_startup_descriptors(startup_descriptors, iteration, options):
+  lines = [
+      startup_descriptor_to_string(startup_descriptor, flags)
+      for startup_descriptor, flags in startup_descriptors.items()]
   write_tmp_textual_artifact(
-      startup_descriptors, iteration, options, 'startup-descriptors.txt')
+      lines, iteration, options, 'startup-descriptors.txt')
+
+def startup_descriptor_to_string(startup_descriptor, flags):
+  result = ''
+  if flags['conditional_startup']:
+    pass # result += 'C'
+  if flags['post_startup']:
+    pass # result += 'P'
+  result += startup_descriptor
+  return result
 
 def parse_options(argv):
   result = argparse.ArgumentParser(
@@ -177,6 +272,11 @@
   result.add_argument('--logcat',
                       action='store_true',
                       default=False)
+  result.add_argument('--include-conditional-startup',
+                      help='Include conditional startup classes and methods in '
+                           'the R8 startup descriptors',
+                      action='store_true',
+                      default=False)
   result.add_argument('--include-post-startup',
                       help='Include post startup classes and methods in the R8 '
                            'startup descriptors',
@@ -224,7 +324,7 @@
   if options.apk:
     adb_utils.uninstall(options.app_id, options.device_id)
     adb_utils.install(options.apk, options.device_id)
-  startup_descriptors = set()
+  startup_descriptors = {}
   if options.until_stable:
     iteration = 0
     stable_iterations = 0
@@ -242,12 +342,12 @@
       extend_startup_descriptors(startup_descriptors, iteration, options)
   if options.out is not None:
     with open(options.out, 'w') as f:
-      for startup_descriptor in startup_descriptors:
-        f.write(startup_descriptor)
+      for startup_descriptor, flags in startup_descriptors.items():
+        f.write(startup_descriptor_to_string(startup_descriptor, flags))
         f.write('\n')
   else:
-    for startup_descriptor in startup_descriptors:
-      print(startup_descriptor)
+    for startup_descriptor, flags in startup_descriptors.items():
+      print(startup_descriptor_to_string(startup_descriptor, flags))
 
 if __name__ == '__main__':
   sys.exit(main(sys.argv[1:]))
diff --git a/tools/test.py b/tools/test.py
index 570da44..27c8b6a 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -359,9 +359,9 @@
     gradle_args.append('-Ptesting-state-name=' + options.testing_state_name)
 
   # Build an R8 with dependencies for bootstrapping tests before adding test sources.
-  gradle_args.append('r8WithDeps')
-  gradle_args.append('r8WithDeps11')
   gradle_args.append('r8WithRelocatedDeps')
+  gradle_args.append('r8WithRelocatedDeps17')
+  # TODO(b/227160052): Remove 11 once the bootstrap test runs on 17.
   gradle_args.append('r8WithRelocatedDeps11')
 
   # Add Gradle tasks