Merge commit 'cfdab6475040d4d12daf817581fe6819fd94cbf0' into dev-release
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index ff1df31..aa285e0 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -1699,6 +1699,35 @@
       }
     }
     builders {
+      name: "smali"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-20.04"
+      dimensions: "pool:luci.r8.ci"
+      exe {
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "builder_group": "internal.client.smali",'
+        '  "recipe": "rex",'
+        '  "test_wrapper": "tools/archive_smali.py"'
+        '}'
+      priority: 26
+      execution_timeout_secs: 43200
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+    }
+    builders {
       name: "windows"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index b1481fe..4073c3d 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -126,6 +126,11 @@
     short_name: "kotlin_old"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/smali"
+    category: "R8"
+    short_name: "smali"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/lib_desugar-archive-jdk11"
     category: "library_desugar"
     short_name: "jdk11"
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg
index fa8d5b0..5417911 100644
--- a/infra/config/global/generated/luci-notify.cfg
+++ b/infra/config/global/generated/luci-notify.cfg
@@ -540,6 +540,18 @@
   }
   builders {
     bucket: "ci"
+    name: "smali"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
     name: "windows"
     repository: "https://r8.googlesource.com/r8"
   }
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index dbd8f00..982d085 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -688,6 +688,21 @@
   }
 }
 job {
+  id: "smali"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 1
+    max_batch_size: 1
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "smali"
+  }
+}
+job {
   id: "windows"
   realm: "ci"
   acl_sets: "ci"
@@ -805,6 +820,7 @@
   triggers: "linux-kotlin_old"
   triggers: "linux-none"
   triggers: "linux-run-on-app-dump"
+  triggers: "smali"
   triggers: "windows"
   gitiles {
     repo: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index 11b6c85..cfee79c 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@
 name: "r8"
 access: "group:all"
 lucicfg {
-  version: "1.38.2"
+  version: "1.39.4"
   package_dir: ".."
   config_dir: "generated"
   entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 32abb36..1e374ed 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -169,7 +169,7 @@
     "--archive_failures"
 ]
 
-def get_dimensions(windows=False, internal=False, normal=False):
+def get_dimensions(windows=False, internal=False, normal=False, smali=False):
   dimensions = {
     "cores" : "2" if internal else "8",
     "cpu" : "x86-64",
@@ -410,6 +410,22 @@
     }
 )
 
+r8_builder(
+    "smali",
+    dimensions = get_dimensions(smali=True),
+    triggering_policy = scheduler.policy(
+        kind = scheduler.GREEDY_BATCHING_KIND,
+        max_concurrent_invocations = 1,
+        max_batch_size = 1,
+    ),
+    properties = {
+        "test_wrapper" : "tools/archive_smali.py",
+        "builder_group" : "internal.client.smali"
+    },
+    execution_timeout = time.hour * 12,
+    expiration_timeout = time.hour * 35,
+)
+
 order_of_categories = [
   "archive",
   "R8",
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 285ef1b..cc62da7 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MapConsumerUtils;
 import com.android.tools.r8.utils.ProgramConsumerUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -281,28 +282,25 @@
     private int minApiLevel = 0;
     private int threadCount = ThreadUtils.NOT_SPECIFIED;
     protected DesugarState desugarState = DesugarState.ON;
-    private List<StringResource> desugaredLibrarySpecificationResources = new ArrayList<>();
+    private final List<StringResource> desugaredLibrarySpecificationResources = new ArrayList<>();
     private boolean includeClassesChecksum = false;
     private boolean optimizeMultidexForLinearAlloc = false;
     private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
-    private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
-    private List<Consumer<Inspector>> outputInspections = new ArrayList<>();
+    private final List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
+    private final List<Consumer<Inspector>> outputInspections = new ArrayList<>();
     protected StringConsumer proguardMapConsumer = null;
+    protected PartitionMapConsumer partitionMapConsumer = null;
     private DumpInputFlags dumpInputFlags = DumpInputFlags.getDefault();
     private MapIdProvider mapIdProvider = null;
     private SourceFileProvider sourceFileProvider = null;
     private boolean isAndroidPlatformBuild = false;
-    private List<ArtProfileForRewriting> artProfilesForRewriting = new ArrayList<>();
-    private List<StartupProfileProvider> startupProfileProviders = new ArrayList<>();
+    private final List<ArtProfileForRewriting> artProfilesForRewriting = new ArrayList<>();
+    private final List<StartupProfileProvider> startupProfileProviders = new ArrayList<>();
     private ClassConflictResolver classConflictResolver = null;
     private CancelCompilationChecker cancelCompilationChecker = null;
 
     abstract CompilationMode defaultCompilationMode();
 
-    Builder() {
-      mode = defaultCompilationMode();
-    }
-
     Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
       mode = defaultCompilationMode();
@@ -395,6 +393,33 @@
     }
 
     /**
+     * Set an output destination to which partition-map content should be written.
+     *
+     * <p>This is a short-hand for setting a {@link PartitionMapConsumer} using {@link
+     * #setPartitionMapConsumer}. Note that any subsequent call to this method or {@link
+     * #setPartitionMapConsumer} will override the previous setting.
+     *
+     * @param partitionMapOutput File-system path to write output at.
+     */
+    B setPartitionMapOutputPath(Path partitionMapOutput) {
+      assert partitionMapOutput != null;
+      return setPartitionMapConsumer(MapConsumerUtils.createZipConsumer(partitionMapOutput));
+    }
+
+    /**
+     * Set a consumer for receiving the partition map content.
+     *
+     * <p>Note that any subsequent call to this method or {@link #setPartitionMapOutputPath} will
+     * override the previous setting.
+     *
+     * @param partitionMapConsumer Consumer to receive the content once produced.
+     */
+    B setPartitionMapConsumer(PartitionMapConsumer partitionMapConsumer) {
+      this.partitionMapConsumer = partitionMapConsumer;
+      return self();
+    }
+
+    /**
      * Get the main dex list consumer that will receive the final complete main dex list.
      */
     public StringConsumer getMainDexListConsumer() {
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2af2e26..2e50083 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -302,9 +302,7 @@
         timing.begin("Generate main-dex list");
         appView.dexItemFactory().clearTypeElementsCache();
         MainDexInfo mainDexInfo =
-            new GenerateMainDexList(options)
-                .traceMainDex(
-                    executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
+            new GenerateMainDexList(options).traceMainDexForD8(appView, executor);
         appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
         timing.end();
       }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 137cb8b..a97ec22 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -665,13 +665,10 @@
     internal.setSyntheticInfoConsumer(syntheticInfoConsumer);
     internal.desugarGraphConsumer = desugarGraphConsumer;
     internal.mainDexKeepRules = mainDexKeepRules;
-    internal.proguardMapConsumer =
+    internal.mapConsumer =
         proguardMapConsumer == null
             ? null
-            : ProguardMapStringConsumer.builder()
-                .setStringConsumer(proguardMapConsumer)
-                .setDiagnosticsHandler(getReporter())
-                .build();
+            : ProguardMapStringConsumer.builder().setStringConsumer(proguardMapConsumer).build();
     internal.lineNumberOptimization =
         !internal.debug && proguardMapConsumer != null
             ? LineNumberOptimization.ON
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 1e0f9c9..182d156 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.StringConsumer.ForwardingConsumer;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
@@ -47,7 +48,7 @@
       throws IOException {
     try {
       DexApplication application = new ApplicationReader(app, options, timing).read(executor);
-      traceMainDex(executor, application, MainDexInfo.none())
+      traceMainDexForGenerateMainDexList(executor, application)
           .forEach(type -> consumer.accept(type.toBinaryName() + ".class", options.reporter));
       consumer.finished(options.reporter);
     } catch (ExecutionException e) {
@@ -55,11 +56,22 @@
     }
   }
 
-  public MainDexInfo traceMainDex(
-      ExecutorService executor, DexApplication application, MainDexInfo existingMainDexInfo)
+  public MainDexInfo traceMainDexForD8(AppView<AppInfo> appView, ExecutorService executor)
       throws ExecutionException {
-    AppView<? extends AppInfoWithClassHierarchy> appView =
-        AppView.createForR8(application.toDirect(), existingMainDexInfo);
+    return traceMainDex(
+        AppView.createForSimulatingR8InD8(
+            appView.app().toDirect(), appView.appInfo().getMainDexInfo()),
+        executor);
+  }
+
+  public MainDexInfo traceMainDexForGenerateMainDexList(
+      ExecutorService executor, DexApplication application) throws ExecutionException {
+    return traceMainDex(AppView.createForR8(application.toDirect()), executor);
+  }
+
+  private MainDexInfo traceMainDex(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executor)
+      throws ExecutionException {
     appView.setAppServices(AppServices.builder(appView).build());
 
     MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
diff --git a/src/main/java/com/android/tools/r8/MapConsumerToPartitionMapConsumer.java b/src/main/java/com/android/tools/r8/MapConsumerToPartitionMapConsumer.java
new file mode 100644
index 0000000..fcd3113
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MapConsumerToPartitionMapConsumer.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MapConsumer;
+import com.android.tools.r8.naming.ProguardMapMarkerInfo;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.internal.ProguardMapProducerInternal;
+import java.io.IOException;
+
+public class MapConsumerToPartitionMapConsumer implements MapConsumer {
+
+  protected final PartitionMapConsumer partitionMapConsumer;
+
+  protected MapConsumerToPartitionMapConsumer(PartitionMapConsumer partitionMapConsumer) {
+    assert partitionMapConsumer != null;
+    this.partitionMapConsumer = partitionMapConsumer;
+  }
+
+  @Override
+  public void accept(
+      DiagnosticsHandler diagnosticsHandler,
+      ProguardMapMarkerInfo makerInfo,
+      ClassNameMapper classNameMapper) {
+    try {
+      classNameMapper.setPreamble(makerInfo.toPreamble());
+      partitionMapConsumer.acceptMappingPartitionMetadata(
+          ProguardMapPartitioner.builder(diagnosticsHandler)
+              .setProguardMapProducer(new ProguardMapProducerInternal(classNameMapper))
+              .setPartitionConsumer(partitionMapConsumer::acceptMappingPartition)
+              // Modifying these do not actually do anything currently since there is no parsing.
+              .setAllowEmptyMappedRanges(false)
+              .setAllowExperimentalMapping(false)
+              .build()
+              .run());
+    } catch (IOException exception) {
+      throw new Unreachable("IOExceptions should only occur when parsing");
+    }
+  }
+
+  @Override
+  public void finished(DiagnosticsHandler handler) {
+    partitionMapConsumer.finished(handler);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/MappingSupplierInternal.java b/src/main/java/com/android/tools/r8/MappingSupplierInternal.java
new file mode 100644
index 0000000..116a964
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MappingSupplierInternal.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import java.util.Set;
+
+public interface MappingSupplierInternal {
+
+  void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler);
+
+  Set<MapVersionMappingInformation> getMapVersions(DiagnosticsHandler diagnosticsHandler);
+}
diff --git a/src/main/java/com/android/tools/r8/PartitionMapConsumer.java b/src/main/java/com/android/tools/r8/PartitionMapConsumer.java
new file mode 100644
index 0000000..ea232d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/PartitionMapConsumer.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.retrace.MappingPartition;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+
+@Keep
+public interface PartitionMapConsumer extends Finishable {
+
+  void acceptMappingPartition(MappingPartition mappingPartition);
+
+  void acceptMappingPartitionMetadata(MappingPartitionMetadata mappingPartitionMetadata);
+}
diff --git a/src/main/java/com/android/tools/r8/ProguardMapConsumer.java b/src/main/java/com/android/tools/r8/ProguardMapConsumer.java
deleted file mode 100644
index f00b428..0000000
--- a/src/main/java/com/android/tools/r8/ProguardMapConsumer.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8;
-
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ProguardMapMarkerInfo;
-
-public abstract class ProguardMapConsumer implements Finishable {
-
-  public abstract void accept(ProguardMapMarkerInfo makerInfo, ClassNameMapper classNameMapper);
-}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index edec018..39e7996 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import static com.android.tools.r8.utils.InternalOptions.DETERMINISTIC_DEBUGGING;
+import static com.android.tools.r8.utils.MapConsumerUtils.wrapExistingMapConsumerIfNotNull;
 
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
+import com.android.tools.r8.naming.MapConsumer;
 import com.android.tools.r8.naming.ProguardMapStringConsumer;
 import com.android.tools.r8.naming.SourceFileRewriter;
 import com.android.tools.r8.origin.Origin;
@@ -299,6 +301,34 @@
     }
 
     /**
+     * Set an output destination to which r8-map content should be written.
+     *
+     * <p>This is a short-hand for setting a {@link MapConsumerToPartitionMapConsumer} using {@link
+     * #setPartitionMapConsumer}. Note that any subsequent call to this method or {@link
+     * #setPartitionMapConsumer} will override the previous setting.
+     *
+     * @param partitionMapOutput File-system path to write output at.
+     */
+    @Override
+    public Builder setPartitionMapOutputPath(Path partitionMapOutput) {
+      assert partitionMapOutput != null;
+      return super.setPartitionMapOutputPath(partitionMapOutput);
+    }
+
+    /**
+     * Set a consumer for receiving the r8-map content.
+     *
+     * <p>Note that any subsequent call to this method or {@link #setPartitionMapOutputPath} will
+     * override the previous setting.
+     *
+     * @param partitionMapConsumer Consumer to receive the content once produced.
+     */
+    @Override
+    public Builder setPartitionMapConsumer(PartitionMapConsumer partitionMapConsumer) {
+      return super.setPartitionMapConsumer(partitionMapConsumer);
+    }
+
+    /**
      * Set a consumer for receiving the keep rules to use when compiling the desugared library for
      * the program being compiled in this compilation.
      *
@@ -632,6 +662,7 @@
               forceProguardCompatibility,
               includeDataResources,
               proguardMapConsumer,
+              partitionMapConsumer,
               proguardUsageConsumer,
               proguardSeedsConsumer,
               proguardConfigurationConsumer,
@@ -832,6 +863,7 @@
   private final boolean forceProguardCompatibility;
   private final Optional<Boolean> includeDataResources;
   private final StringConsumer proguardMapConsumer;
+  private final PartitionMapConsumer partitionMapConsumer;
   private final StringConsumer proguardUsageConsumer;
   private final StringConsumer proguardSeedsConsumer;
   private final StringConsumer proguardConfigurationConsumer;
@@ -912,6 +944,7 @@
       boolean forceProguardCompatibility,
       Optional<Boolean> includeDataResources,
       StringConsumer proguardMapConsumer,
+      PartitionMapConsumer partitionMapConsumer,
       StringConsumer proguardUsageConsumer,
       StringConsumer proguardSeedsConsumer,
       StringConsumer proguardConfigurationConsumer,
@@ -969,6 +1002,7 @@
     this.forceProguardCompatibility = forceProguardCompatibility;
     this.includeDataResources = includeDataResources;
     this.proguardMapConsumer = proguardMapConsumer;
+    this.partitionMapConsumer = partitionMapConsumer;
     this.proguardUsageConsumer = proguardUsageConsumer;
     this.proguardSeedsConsumer = proguardSeedsConsumer;
     this.proguardConfigurationConsumer = proguardConfigurationConsumer;
@@ -992,6 +1026,7 @@
     forceProguardCompatibility = false;
     includeDataResources = null;
     proguardMapConsumer = null;
+    partitionMapConsumer = null;
     proguardUsageConsumer = null;
     proguardSeedsConsumer = null;
     proguardConfigurationConsumer = null;
@@ -1070,13 +1105,15 @@
             proguardMapConsumer,
             proguardConfiguration.isPrintMapping(),
             proguardConfiguration.getPrintMappingFile());
-    internal.proguardMapConsumer =
-        stringConsumer == null
-            ? null
-            : ProguardMapStringConsumer.builder()
-                .setStringConsumer(stringConsumer)
-                .setDiagnosticsHandler(getReporter())
-                .build();
+    MapConsumer mapConsumer =
+        wrapExistingMapConsumerIfNotNull(
+            internal.mapConsumer, partitionMapConsumer, MapConsumerToPartitionMapConsumer::new);
+    internal.mapConsumer =
+        wrapExistingMapConsumerIfNotNull(
+            mapConsumer,
+            stringConsumer,
+            nonNullStringConsumer ->
+                ProguardMapStringConsumer.builder().setStringConsumer(stringConsumer).build());
 
     // Amend the usage information consumer with options from the proguard configuration.
     internal.usageInformationConsumer =
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index caa1c27..c4c8170 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.ProguardMapConsumer;
 import com.android.tools.r8.bisect.BisectOptions.Result;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
@@ -14,6 +13,7 @@
 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.naming.MapConsumer;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -184,7 +184,7 @@
     InternalOptions options = app.options;
     // Save the original consumers, so they can be unwrapped after write.
     ProgramConsumer programConsumer = options.programConsumer;
-    ProguardMapConsumer proguardMapConsumer = options.proguardMapConsumer;
+    MapConsumer mapConsumer = options.mapConsumer;
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     ApplicationWriter writer =
         ApplicationWriter.create(
@@ -196,7 +196,7 @@
     compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
     // Restore original consumers.
     options.programConsumer = programConsumer;
-    options.proguardMapConsumer = proguardMapConsumer;
+    options.mapConsumer = mapConsumer;
   }
 
   public static DexProgramClass run(BisectOptions options) throws Exception {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 32afbc5..a173410 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -106,6 +106,10 @@
   }
 
   public int getAsmOpcode() {
+    return getAsmOpcode(opcode, type);
+  }
+
+  public static int getAsmOpcode(Opcode opcode, NumericType type) {
     switch (opcode) {
       case Shl:
         return type.isWide() ? Opcodes.LSHL : Opcodes.ISHL;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index a664d9d..37fad8c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -289,7 +289,7 @@
   }
 
   private boolean willComputeProguardMap() {
-    return options.proguardMapConsumer != null;
+    return options.mapConsumer != null;
   }
 
   /** Writer that never needs the input app to deal with mapping info for kotlin. */
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 7a7b8d6..aff2b31 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -228,6 +228,24 @@
         defaultTypeRewriter(appInfo));
   }
 
+  public static AppView<AppInfoWithClassHierarchy> createForSimulatingR8InD8(
+      DirectMappedDexApplication application, MainDexInfo mainDexInfo) {
+    ClassToFeatureSplitMap classToFeatureSplitMap =
+        ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(application.options);
+    AppInfoWithClassHierarchy appInfo =
+        AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+            application,
+            classToFeatureSplitMap,
+            mainDexInfo,
+            GlobalSyntheticsStrategy.forSingleOutputMode());
+    return new AppView<>(
+        appInfo,
+        ArtProfileCollection.empty(),
+        StartupProfile.empty(),
+        WholeProgramOptimizations.ON,
+        defaultTypeRewriter(appInfo));
+  }
+
   public static <T extends AppInfo> AppView<T> createForD8(
       T appInfo, TypeRewriter mapper, Timing timing) {
     return new AppView<>(
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 486aaa7..9532f1e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -312,6 +312,17 @@
     return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events);
   }
 
+  public DexDebugInfo debugInfoWithExtraParameters(DexItemFactory factory, int extraParameters) {
+    EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
+    if (eventBasedInfo == null) {
+      return eventBasedInfo;
+    }
+    DexString[] parameters = eventBasedInfo.parameters;
+    DexString[] newParameters = new DexString[parameters.length + extraParameters];
+    System.arraycopy(parameters, 0, newParameters, 0, parameters.length);
+    return new EventBasedDebugInfo(eventBasedInfo.startLine, newParameters, eventBasedInfo.events);
+  }
+
   @Override
   public Code getCodeAsInlining(
       DexMethod caller, DexMethod callee, DexItemFactory factory, boolean isCalleeD8R8Synthesized) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index f9a5279..1c502d5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1011,6 +1011,20 @@
     }
   }
 
+  public static void setDebugInfoWithExtraParameters(
+      Code code, int arity, int extraParameters, AppView<?> appView) {
+    if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      DexDebugInfo newDebugInfo =
+          dexCode.debugInfoWithExtraParameters(appView.dexItemFactory(), extraParameters);
+      assert (newDebugInfo == null) || (arity == newDebugInfo.getParameterCount());
+      dexCode.setDebugInfo(newDebugInfo);
+    } else {
+      assert code.isCfCode();
+      // We don't have anything to do for Cf.
+    }
+  }
+
   private DexCode toDexCodeThatLogsError(DexItemFactory itemFactory) {
     checkIfObsolete();
     Signature signature = MethodSignature.fromDexMethod(getReference());
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index f49fcbd..200b351 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -32,6 +32,10 @@
     this.removedMethods = removedMethods;
   }
 
+  public static Builder concurrentBuilder() {
+    return new ConcurrentBuilder();
+  }
+
   public static Builder builder() {
     return new Builder();
   }
@@ -111,15 +115,22 @@
 
     private DexApplication prunedApp;
 
-    private final Set<DexReference> additionalPinnedItems = Sets.newIdentityHashSet();
-    private final Set<DexType> noLongerSyntheticItems = Sets.newIdentityHashSet();
-    private Set<DexType> removedClasses = Sets.newIdentityHashSet();
-    private final Set<DexField> removedFields = Sets.newIdentityHashSet();
-    private Set<DexMethod> removedMethods = Sets.newIdentityHashSet();
+    private final Set<DexReference> additionalPinnedItems;
+    private final Set<DexType> noLongerSyntheticItems;
+    private Set<DexType> removedClasses;
+    private final Set<DexField> removedFields;
+    private Set<DexMethod> removedMethods;
 
-    Builder() {}
+    Builder() {
+      additionalPinnedItems = newEmptySet();
+      noLongerSyntheticItems = newEmptySet();
+      removedClasses = newEmptySet();
+      removedFields = newEmptySet();
+      removedMethods = newEmptySet();
+    }
 
     Builder(PrunedItems prunedItems) {
+      this();
       additionalPinnedItems.addAll(prunedItems.getAdditionalPinnedItems());
       noLongerSyntheticItems.addAll(prunedItems.getNoLongerSyntheticItems());
       prunedApp = prunedItems.getPrunedApp();
@@ -128,6 +139,10 @@
       removedMethods.addAll(prunedItems.getRemovedMethods());
     }
 
+    <T> Set<T> newEmptySet() {
+      return Sets.newIdentityHashSet();
+    }
+
     public Builder setPrunedApp(DexApplication prunedApp) {
       this.prunedApp = prunedApp;
       return this;
@@ -196,4 +211,22 @@
           removedMethods);
     }
   }
+
+  public static class ConcurrentBuilder extends Builder {
+
+    @Override
+    <T> Set<T> newEmptySet() {
+      return Sets.newConcurrentHashSet();
+    }
+
+    @Override
+    public Builder setRemovedClasses(Set<DexType> removedClasses) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Builder setRemovedMethods(Set<DexMethod> removedMethods) {
+      throw new UnsupportedOperationException();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java
new file mode 100644
index 0000000..8a4c7bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/fixup/ConcurrentMethodFixup.java
@@ -0,0 +1,219 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.fixup;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class ConcurrentMethodFixup {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final Map<ClasspathOrLibraryClass, DexMethodSignatureSet> nonProgramVirtualMethods =
+      new ConcurrentHashMap<>();
+  private final ProgramClassFixer programClassFixer;
+
+  public ConcurrentMethodFixup(
+      AppView<AppInfoWithLiveness> appView, ProgramClassFixer programClassFixer) {
+    this.appView = appView;
+    this.programClassFixer = programClassFixer;
+  }
+
+  public void fixupClassesConcurrentlyByConnectedProgramComponents(
+      Timing timing, ExecutorService executorService) throws ExecutionException {
+    timing.begin("Concurrent method fixup");
+    timing.begin("Compute strongly connected components");
+    ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+        ImmediateProgramSubtypingInfo.create(appView);
+    List<Set<DexProgramClass>> connectedComponents =
+        new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
+            .computeStronglyConnectedComponents();
+    timing.end();
+
+    timing.begin("Process strongly connected components");
+    ThreadUtils.processItems(
+        connectedComponents, this::processConnectedProgramComponents, executorService);
+    timing.end();
+    timing.end();
+  }
+
+  public interface ProgramClassFixer {
+    // When a class is fixed-up, it is guaranteed that its supertype and interfaces were processed
+    // before. In addition, all interfaces are processed before any class is processed.
+    void fixupProgramClass(DexProgramClass clazz, MethodNamingUtility namingUtility);
+
+    // Answers true if the method should be reserved as itself.
+    boolean shouldReserveAsIfPinned(ProgramMethod method);
+  }
+
+  private void processConnectedProgramComponents(Set<DexProgramClass> classes) {
+    List<DexProgramClass> sorted = new ArrayList<>(classes);
+    sorted.sort(Comparator.comparing(DexClass::getType));
+    BiMap<DexMethodSignature, DexMethodSignature> componentSignatures = HashBiMap.create();
+
+    // 1) Reserve all library overrides and pinned virtual methods.
+    reserveComponentPinnedAndInterfaceMethodSignatures(sorted, componentSignatures);
+
+    // 2) Map all interfaces top-down updating the componentSignatures.
+    Set<DexProgramClass> processedInterfaces = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : sorted) {
+      if (clazz.isInterface()) {
+        processInterface(clazz, processedInterfaces, componentSignatures);
+      }
+    }
+
+    // 3) Map all classes top-down propagating the inherited signatures.
+    // The componentSignatures are already fully computed and should not be updated anymore.
+    // TODO(b/279707790): Consider changing the processing to have a different componentSignatures
+    //  per subtree.
+    Set<DexProgramClass> processedClasses = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : sorted) {
+      if (!clazz.isInterface()) {
+        processClass(clazz, processedClasses, componentSignatures);
+      }
+    }
+  }
+
+  private void processClass(
+      DexProgramClass clazz,
+      Set<DexProgramClass> processedClasses,
+      BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+    assert !clazz.isInterface();
+    if (!processedClasses.add(clazz)) {
+      return;
+    }
+    // We need to process first the super-type for the top-down propagation of inherited signatures.
+    DexProgramClass superClass = asProgramClassOrNull(appView.definitionFor(clazz.superType));
+    if (superClass != null) {
+      processClass(superClass, processedClasses, componentSignatures);
+    }
+    MethodNamingUtility utility = createMethodNamingUtility(componentSignatures, clazz);
+    programClassFixer.fixupProgramClass(clazz, utility);
+  }
+
+  private void processInterface(
+      DexProgramClass clazz,
+      Set<DexProgramClass> processedInterfaces,
+      BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+    assert clazz.isInterface();
+    if (!processedInterfaces.add(clazz)) {
+      return;
+    }
+    // We need to process first all super-interfaces to avoid generating collisions by renaming
+    // private or static methods into inherited virtual method signatures.
+    for (DexType superInterface : clazz.getInterfaces()) {
+      DexProgramClass superInterfaceClass =
+          asProgramClassOrNull(appView.definitionFor(superInterface));
+      if (superInterfaceClass != null) {
+        processInterface(superInterfaceClass, processedInterfaces, componentSignatures);
+      }
+    }
+    MethodNamingUtility utility = createMethodNamingUtility(componentSignatures, clazz);
+    programClassFixer.fixupProgramClass(clazz, utility);
+  }
+
+  private boolean shouldReserveAsPinned(ProgramMethod method) {
+    KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+    return !keepInfo.isOptimizationAllowed(appView.options())
+        || !keepInfo.isShrinkingAllowed(appView.options())
+        || programClassFixer.shouldReserveAsIfPinned(method);
+  }
+
+  private MethodNamingUtility createMethodNamingUtility(
+      BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures, DexProgramClass clazz) {
+    BiMap<DexMethod, DexMethod> localSignatures = HashBiMap.create();
+    clazz.forEachProgramInstanceInitializer(
+        method -> {
+          if (shouldReserveAsPinned(method)) {
+            localSignatures.put(method.getReference(), method.getReference());
+          }
+        });
+    return new MethodNamingUtility(appView.dexItemFactory(), inheritedSignatures, localSignatures);
+  }
+
+  private void reserveComponentPinnedAndInterfaceMethodSignatures(
+      List<DexProgramClass> stronglyConnectedProgramClasses,
+      BiMap<DexMethodSignature, DexMethodSignature> componentSignatures) {
+    Set<ClasspathOrLibraryClass> seenNonProgramClasses = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
+      // If a private or static method is pinned, we need to reserve the mapping to avoid creating
+      // a collision with a changed virtual method.
+      clazz.forEachProgramMethodMatching(
+          m -> !m.isInstanceInitializer(),
+          method -> {
+            if (shouldReserveAsPinned(method)) {
+              componentSignatures.put(method.getMethodSignature(), method.getMethodSignature());
+            }
+          });
+      clazz.forEachImmediateSupertype(
+          supertype -> {
+            DexClass superclass = appView.definitionFor(supertype);
+            if (superclass != null
+                && !superclass.isProgramClass()
+                && seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass())) {
+              for (DexMethodSignature vMethod :
+                  getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass())) {
+                componentSignatures.put(vMethod, vMethod);
+              }
+            }
+          });
+    }
+  }
+
+  private DexMethodSignatureSet getOrComputeNonProgramVirtualMethods(
+      ClasspathOrLibraryClass clazz) {
+    DexMethodSignatureSet libraryMethodsOnClass = nonProgramVirtualMethods.get(clazz);
+    if (libraryMethodsOnClass != null) {
+      return libraryMethodsOnClass;
+    }
+    return computeNonProgramVirtualMethods(clazz);
+  }
+
+  private DexMethodSignatureSet computeNonProgramVirtualMethods(
+      ClasspathOrLibraryClass classpathOrLibraryClass) {
+    DexClass clazz = classpathOrLibraryClass.asDexClass();
+    DexMethodSignatureSet libraryMethodsOnClass = DexMethodSignatureSet.create();
+    clazz.forEachImmediateSupertype(
+        supertype -> {
+          DexClass superclass = appView.definitionFor(supertype);
+          if (superclass != null) {
+            assert !superclass.isProgramClass();
+            libraryMethodsOnClass.addAll(
+                getOrComputeNonProgramVirtualMethods(superclass.asClasspathOrLibraryClass()));
+          }
+        });
+    clazz.forEachClassMethodMatching(
+        DexEncodedMethod::belongsToVirtualPool,
+        method -> libraryMethodsOnClass.add(method.getMethodSignature()));
+    nonProgramVirtualMethods.put(classpathOrLibraryClass, libraryMethodsOnClass);
+    return libraryMethodsOnClass;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java
new file mode 100644
index 0000000..977e01c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/fixup/MethodNamingUtility.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.fixup;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.BiMap;
+import java.util.function.BiConsumer;
+
+public class MethodNamingUtility {
+
+  private final DexItemFactory factory;
+  private final BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures;
+  private final BiMap<DexMethod, DexMethod> localSignatures;
+
+  public MethodNamingUtility(
+      DexItemFactory factory,
+      BiMap<DexMethodSignature, DexMethodSignature> inheritedSignatures,
+      BiMap<DexMethod, DexMethod> localSignatures) {
+    this.factory = factory;
+    this.inheritedSignatures = inheritedSignatures;
+    this.localSignatures = localSignatures;
+  }
+
+  public DexMethod nextUniqueMethod(
+      DexEncodedMethod method, DexProto newProto, DexType initExtraType) {
+    DexMethod reference = method.getReference();
+
+    if (method.isClassInitializer()) {
+      assert reference.getProto() == newProto;
+      return reference;
+    }
+
+    if (method.isInstanceInitializer()) {
+      assert initExtraType != null;
+      return nextUniqueInitializer(reference, newProto, initExtraType);
+    }
+
+    if (method.isNonPrivateVirtualMethod()) {
+      return nextUniqueVirtualMethod(reference, newProto);
+    }
+
+    return nextUniquePrivateOrStaticMethod(reference, newProto);
+  }
+
+  private DexMethod nextUniqueInitializer(
+      DexMethod reference, DexProto newProto, DexType initExtraType) {
+    assert !inheritedSignatures.containsKey(reference.getSignature());
+
+    // 1) We check if the reference has already been reserved (pinning).
+    DexMethod remapped = localSignatures.get(reference);
+    if (remapped != null) {
+      assert remapped.getProto() == newProto;
+      return remapped.withHolder(reference.getHolderType(), factory);
+    }
+
+    // 2) We check for collision with already mapped methods.
+    DexMethod newMethod = reference.withProto(newProto, factory);
+    if (localSignatures.containsValue(newMethod)) {
+      // This collides with something that has been renamed into this.
+      newMethod =
+          factory.createInstanceInitializerWithFreshProto(
+              newMethod, initExtraType, tryMethod -> !localSignatures.containsValue(tryMethod));
+    }
+
+    // 3) Finally register the new method and return it.
+    assert !localSignatures.containsValue(newMethod);
+    localSignatures.put(reference, newMethod);
+    return newMethod;
+  }
+
+  private DexMethod nextUniquePrivateOrStaticMethod(DexMethod reference, DexProto newProto) {
+    return nextUniqueMethod(reference, newProto, localSignatures::put);
+  }
+
+  private DexMethod nextUniqueVirtualMethod(DexMethod reference, DexProto newProto) {
+    return nextUniqueMethod(
+        reference,
+        newProto,
+        (from, to) -> inheritedSignatures.put(from.getSignature(), to.getSignature()));
+  }
+
+  private boolean anyCollision(DexMethod method) {
+    return localSignatures.containsValue(method)
+        || inheritedSignatures.containsValue(method.getSignature());
+  }
+
+  private DexMethod nextUniqueMethod(
+      DexMethod reference, DexProto newProto, BiConsumer<DexMethod, DexMethod> registration) {
+    // 1) We check if the reference has already been reserved (pinning or override).
+    DexMethodSignature remappedSignature = inheritedSignatures.get(reference.getSignature());
+    if (remappedSignature != null) {
+      assert remappedSignature.getProto() == newProto;
+      return remappedSignature.withHolder(reference.getHolderType(), factory);
+    }
+
+    // 2) We check for collision with already mapped methods.
+    DexMethod newMethod = reference.withProto(newProto, factory);
+    if (anyCollision(newMethod)) {
+      newMethod =
+          factory.createFreshMethodNameWithoutHolder(
+              newMethod.getName().toString(),
+              newMethod.getProto(),
+              newMethod.getHolderType(),
+              tryMethod -> !anyCollision(tryMethod));
+    }
+
+    // 3) Finally register the new method and return it.
+    assert !anyCollision(newMethod);
+    registration.accept(reference, newMethod);
+    return newMethod;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
similarity index 93%
rename from src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
rename to src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
index f3cc3e9..bf5e3de 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
@@ -2,8 +2,26 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.graph;
+package com.android.tools.r8.graph.fixup;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
+import com.android.tools.r8.graph.RecordComponentInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index a12baa2..482b43f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index 8685b4a..269bf93 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -96,6 +96,9 @@
   }
 
   private AppView<AppInfo> createAppViewForConversion() {
+    assert appView.enableWholeProgramOptimizations();
+    assert appView.hasClassHierarchy();
+
     // At this point the code rewritings described by repackaging and synthetic finalization have
     // not been applied to the code objects. These code rewritings will be applied in the
     // application writer. We therefore simulate that we are in D8, to allow building IR for each of
@@ -106,7 +109,7 @@
     appView.dexItemFactory().clearTypeElementsCache();
 
     AppView<AppInfo> appViewForConversion =
-        AppView.createForD8(
+        AppView.createForSimulatingD8InR8(
             AppInfo.createInitialAppInfo(
                 appView.appInfo().app(), GlobalSyntheticsStrategy.forNonSynthesizing()));
     appViewForConversion.setGraphLens(appView.graphLens());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 2efe2c3..ca151c5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -345,7 +345,7 @@
         Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
         instructionIterator.add(materializingInstruction);
         instructionIterator.add(
-            new ArrayPut(
+            ArrayPut.create(
                 MemberType.OBJECT,
                 newObjectsValue,
                 indexValue,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
index 722dccc..1daf879 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/EnumValuesObjectState.java
@@ -91,6 +91,12 @@
   @Override
   public ObjectState rewrittenWithLens(
       AppView<AppInfoWithLiveness> appView, GraphLens lens, GraphLens codeLens) {
+    if (objectClassForOrdinal.enumHasBeenUnboxed(appView)) {
+      // It does not make sense to keep outdated data on unboxed enums.
+      // We have the exact content of the array but this is not modeled at this point so we simply
+      // return a KnownLengthArrayState.
+      return appView.abstractValueFactory().createKnownLengthArrayState(state.length);
+    }
     ObjectState[] newState = new ObjectState[state.length];
     for (int i = 0; i < state.length; i++) {
       newState[i] = state[i].rewrittenWithLens(appView, lens, codeLens);
@@ -147,6 +153,8 @@
 
     @Override
     public abstract boolean equals(Object obj);
+
+    public abstract boolean enumHasBeenUnboxed(AppView<?> appView);
   }
 
   static class UniformObjectClassForOrdinal extends ObjectClassForOrdinal {
@@ -183,6 +191,11 @@
       UniformObjectClassForOrdinal other = (UniformObjectClassForOrdinal) obj;
       return type == other.type;
     }
+
+    @Override
+    public boolean enumHasBeenUnboxed(AppView<?> appView) {
+      return appView.unboxedEnums().isUnboxedEnum(type);
+    }
   }
 
   static class VariableObjectClassForOrdinal extends ObjectClassForOrdinal {
@@ -227,5 +240,11 @@
       VariableObjectClassForOrdinal other = (VariableObjectClassForOrdinal) obj;
       return Arrays.equals(types, other.types);
     }
+
+    @Override
+    public boolean enumHasBeenUnboxed(AppView<?> appView) {
+      assert types.length > 0;
+      return appView.unboxedEnums().isUnboxedEnum(types[0]);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Add.java b/src/main/java/com/android/tools/r8/ir/code/Add.java
index dc8cb05..98e4cf2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Add.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Add.java
@@ -19,7 +19,17 @@
 
 public class Add extends ArithmeticBinop {
 
-  public Add(NumericType type, Value dest, Value left, Value right) {
+  public static Add create(NumericType type, Value dest, Value left, Value right) {
+    Add add = createNonNormalized(type, dest, left, right);
+    add.normalizeArgumentsForCommutativeBinop();
+    return add;
+  }
+
+  public static Add createNonNormalized(NumericType type, Value dest, Value left, Value right) {
+    return new Add(type, dest, left, right);
+  }
+
+  private Add(NumericType type, Value dest, Value left, Value right) {
     super(type, dest, left, right);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/And.java b/src/main/java/com/android/tools/r8/ir/code/And.java
index f7aac6c..d7cd98c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/And.java
+++ b/src/main/java/com/android/tools/r8/ir/code/And.java
@@ -16,7 +16,17 @@
 
 public class And extends LogicalBinop {
 
-  public And(NumericType type, Value dest, Value left, Value right) {
+  public static And create(NumericType type, Value dest, Value left, Value right) {
+    And and = new And(type, dest, left, right);
+    and.normalizeArgumentsForCommutativeBinop();
+    return and;
+  }
+
+  public static And createNonNormalized(NumericType type, Value dest, Value left, Value right) {
+    return new And(type, dest, left, right);
+  }
+
+  private And(NumericType type, Value dest, Value left, Value right) {
     super(type, dest, left, right);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index f2aa7cf..92f7380 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -17,7 +17,7 @@
 
 public abstract class ArithmeticBinop extends Binop {
 
-  public ArithmeticBinop(NumericType type, Value dest, Value left, Value right) {
+  ArithmeticBinop(NumericType type, Value dest, Value left, Value right) {
     super(type, dest, left, right);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 2bfd3c3..ab357ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.util.Arrays;
 import java.util.Set;
 
@@ -286,4 +287,14 @@
     int index = index().getConstInstruction().asConstNumber().getIntValue();
     return newArraySize <= 0 || index < 0 || newArraySize <= index;
   }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    if (getMemberType().isObject()) {
+      DexType destType = dest().getType().asReferenceType().toDexType(builder.factory());
+      builder.addArrayGetObject(destType, array(), index());
+    } else {
+      builder.addArrayGetPrimitive(getMemberType(), array(), index());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 5f07431..eff1adc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.util.Arrays;
 
 public class ArrayPut extends ArrayAccess {
@@ -36,14 +37,29 @@
 
   private MemberType type;
 
-  public ArrayPut(MemberType type, Value array, Value index, Value value) {
+  public static ArrayPut create(MemberType type, Value array, Value index, Value value) {
+    ArrayPut put = new ArrayPut(type, array, index, value);
+    assert put.verify();
+    return put;
+  }
+
+  public static ArrayPut createWithoutVerification(
+      MemberType type, Value array, Value index, Value value) {
+    return new ArrayPut(type, array, index, value);
+  }
+
+  private ArrayPut(MemberType type, Value array, Value index, Value value) {
     super(null, Arrays.asList(array, index, value));
-    assert type != null;
-    assert array.verifyCompatible(ValueType.OBJECT);
-    assert index.verifyCompatible(ValueType.INT);
     this.type = type;
   }
 
+  private boolean verify() {
+    assert type != null;
+    assert array().verifyCompatible(ValueType.OBJECT);
+    assert index().verifyCompatible(ValueType.INT);
+    return true;
+  }
+
   @Override
   public int opcode() {
     return Opcodes.ARRAY_PUT;
@@ -251,6 +267,11 @@
 
   @Override
   public ArrayAccess withMemberType(MemberType newMemberType) {
-    return new ArrayPut(newMemberType, array(), index(), value());
+    return ArrayPut.create(newMemberType, array(), index(), value());
+  }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addArrayPut(getMemberType(), array(), index(), value());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 613ff5b..1629cda 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -20,15 +20,19 @@
 
   protected final NumericType type;
 
-  public Binop(NumericType type, Value dest, Value left, Value right) {
+  Binop(NumericType type, Value dest, Value left, Value right) {
     super(dest);
     this.type = type;
-    if (isCommutative() && (!right.isConstNumber() && left.isConstNumber())) {
-      addInValue(right);
-      addInValue(left);
-    } else {
-      addInValue(left);
-      addInValue(right);
+    addInValue(left);
+    addInValue(right);
+  }
+
+  public void normalizeArgumentsForCommutativeBinop() {
+    assert isCommutative();
+    if (isCommutative() && !rightValue().isConstNumber() && leftValue().isConstNumber()) {
+      Value tmp = inValues.get(0);
+      inValues.set(0, inValues.get(1));
+      inValues.set(1, tmp);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index d9a1063..0bb5989 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -285,6 +286,11 @@
     registry.registerCheckCast(type, ignoreCompatRules);
   }
 
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addCheckCast(type, object());
+  }
+
   public static class Builder extends BuilderBase<Builder, CheckCast> {
 
     protected DexType castType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index c9a1b03..5457a36 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -12,11 +12,12 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.util.function.Function;
 
 public abstract class LogicalBinop extends Binop {
 
-  public LogicalBinop(NumericType type, Value dest, Value left, Value right) {
+  LogicalBinop(NumericType type, Value dest, Value left, Value right) {
     super(type, dest, left, right);
   }
 
@@ -143,4 +144,9 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfLogicalBinop(getCfOpcode(), type), this);
   }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addLogicalBinop(getCfOpcode(), type, leftValue(), rightValue());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Mul.java b/src/main/java/com/android/tools/r8/ir/code/Mul.java
index ccddf6a..41e4b35 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Mul.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Mul.java
@@ -19,7 +19,17 @@
 
 public class Mul extends ArithmeticBinop {
 
-  public Mul(NumericType type, Value dest, Value left, Value right) {
+  public static Mul create(NumericType type, Value dest, Value left, Value right) {
+    Mul mul = new Mul(type, dest, left, right);
+    mul.normalizeArgumentsForCommutativeBinop();
+    return mul;
+  }
+
+  public static Mul createNonNormalized(NumericType type, Value dest, Value left, Value right) {
+    return new Mul(type, dest, left, right);
+  }
+
+  private Mul(NumericType type, Value dest, Value left, Value right) {
     super(type, dest, left, right);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Or.java b/src/main/java/com/android/tools/r8/ir/code/Or.java
index 3fd1a77..f0ce553 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Or.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Or.java
@@ -15,7 +15,17 @@
 
 public class Or extends LogicalBinop {
 
-  public Or(NumericType type, Value dest, Value left, Value right) {
+  public static Or create(NumericType type, Value dest, Value left, Value right) {
+    Or or = new Or(type, dest, left, right);
+    or.normalizeArgumentsForCommutativeBinop();
+    return or;
+  }
+
+  public static Or createNonNormalized(NumericType type, Value dest, Value left, Value right) {
+    return new Or(type, dest, left, right);
+  }
+
+  private Or(NumericType type, Value dest, Value left, Value right) {
     super(type, dest, left, right);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Xor.java b/src/main/java/com/android/tools/r8/ir/code/Xor.java
index 657374a..db92998 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Xor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Xor.java
@@ -15,7 +15,17 @@
 
 public class Xor extends LogicalBinop {
 
-  public Xor(NumericType type, Value dest, Value left, Value right) {
+  public static Xor create(NumericType type, Value dest, Value left, Value right) {
+    Xor xor = new Xor(type, dest, left, right);
+    xor.normalizeArgumentsForCommutativeBinop();
+    return xor;
+  }
+
+  public static Xor createNonNormalized(NumericType type, Value dest, Value left, Value right) {
+    return new Xor(type, dest, left, right);
+  }
+
+  private Xor(NumericType type, Value dest, Value left, Value right) {
     super(type, dest, left, right);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 3dfe55f..81f5c14 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -321,7 +321,7 @@
 
         // Replace Not with Xor.
         it.replaceCurrentInstruction(
-            new Xor(current.asNot().type, current.outValue(), inValue, constValue));
+            Xor.create(current.asNot().type, current.outValue(), inValue, constValue));
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 779beba..cf7a34f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1086,7 +1086,7 @@
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    Add instruction = new Add(type, out, in1, in2);
+    Add instruction = Add.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -1096,7 +1096,7 @@
     Value in1 = readNumericRegister(value, type);
     Value in2 = readIntLiteral(constant);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    Add instruction = new Add(type, out, in1, in2);
+    Add instruction = Add.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -1106,7 +1106,7 @@
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    And instruction = new And(type, out, in1, in2);
+    And instruction = And.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -1116,7 +1116,7 @@
     Value in1 = readNumericRegister(value, type);
     Value in2 = readIntLiteral(constant);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    And instruction = new And(type, out, in1, in2);
+    And instruction = And.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -1158,7 +1158,7 @@
     Value inValue = readRegister(value, ValueTypeConstraint.fromMemberType(type));
     Value inArray = readRegister(array, ValueTypeConstraint.OBJECT);
     Value inIndex = readRegister(index, ValueTypeConstraint.INT);
-    ArrayPut instruction = new ArrayPut(type, inArray, inIndex, inValue);
+    ArrayPut instruction = ArrayPut.create(type, inArray, inIndex, inValue);
     if (!type.isPrecise()) {
       addImpreciseInstruction(instruction);
     }
@@ -1323,7 +1323,7 @@
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    Mul instruction = new Mul(type, out, in1, in2);
+    Mul instruction = Mul.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -1333,7 +1333,7 @@
     Value in1 = readNumericRegister(value, type);
     Value in2 = readIntLiteral(constant);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    Mul instruction = new Mul(type, out, in1, in2);
+    Mul instruction = Mul.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -1774,7 +1774,7 @@
       instruction = new Not(type, out, in);
     } else {
       Value minusOne = readLiteral(ValueTypeConstraint.fromNumericType(type), -1);
-      instruction = new Xor(type, out, in, minusOne);
+      instruction = Xor.create(type, out, in, minusOne);
     }
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1991,7 +1991,7 @@
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    Or instruction = new Or(type, out, in1, in2);
+    Or instruction = Or.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -2001,7 +2001,7 @@
     Value in1 = readNumericRegister(value, type);
     Value in2 = readIntLiteral(constant);
     Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-    Or instruction = new Or(type, out, in1, in2);
+    Or instruction = Or.create(type, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
   }
@@ -2077,7 +2077,7 @@
         && in2.getConstInstruction().asConstNumber().isIntegerNegativeOne(type)) {
       instruction = new Not(type, out, in1);
     } else {
-      instruction = new Xor(type, out, in1, in2);
+      instruction = Xor.create(type, out, in1, in2);
     }
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -2093,7 +2093,7 @@
     } else {
       Value in2 = readIntLiteral(constant);
       Value out = writeNumericRegister(dest, type, ThrowingInfo.NO_THROW);
-      instruction = new Xor(type, out, in1, in2);
+      instruction = Xor.create(type, out, in1, in2);
     }
     assert !instruction.instructionTypeCanThrow();
     addInstruction(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index fc8d1f2..a5417c6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1041,6 +1041,7 @@
       OptimizationFeedback feedback,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
+    IRCode oldCode = code;
     if (options.testing.roundtripThroughLir) {
       code = roundtripThroughLir(code, feedback, bytecodeMetadataProvider, timing);
     }
@@ -1075,6 +1076,9 @@
     LirCode<EV> lirCode =
         IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.dexItemFactory());
     timing.end();
+    // Check that printing does not fail.
+    String lirString = lirCode.toString();
+    assert !lirString.isEmpty();
     timing.begin("LIR->IR (" + name + ")");
     IRCode irCode =
         Lir2IRConverter.translate(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 3e410e8..01a25e7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -359,8 +359,8 @@
     }
 
     @Override
-    public void acceptCompanionClassClinit(ProgramMethod method) {
-      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    public void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(companionMethod);
     }
 
     @Override
@@ -496,7 +496,7 @@
     }
 
     @Override
-    public void acceptCompanionClassClinit(ProgramMethod method) {
+    public void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
index 58f8ac7..dc4e201 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/AbstractGenerateFiles.java
@@ -33,7 +33,11 @@
 
   private final DexItemFactory factory = new DexItemFactory();
   private final Reporter reporter = new Reporter();
-  final InternalOptions options = new InternalOptions(factory, reporter);
+  final InternalOptions options =
+      new InternalOptions(factory, reporter)
+          .getArtProfileOptions()
+          .setAllowReadingEmptyArtProfileProvidersMultipleTimesForTesting(true)
+          .getOptions();
 
   final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
   final Path desugaredLibrarySpecificationPath;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 6eb7bca..21771de 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -565,7 +565,9 @@
         appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
         appView,
         methodBuilder -> createCompanionClassInitializer(iface, methodBuilder),
-        eventConsumer::acceptCompanionClassClinit);
+        companionMethod ->
+            eventConsumer.acceptCompanionClassClinit(
+                iface.getProgramClassInitializer(), companionMethod));
   }
 
   private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringBaseEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringBaseEventConsumer.java
index 8b02f6c..76f0d7c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringBaseEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringBaseEventConsumer.java
@@ -7,7 +7,7 @@
 
 public interface InterfaceMethodDesugaringBaseEventConsumer {
 
-  void acceptCompanionClassClinit(ProgramMethod method);
+  void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod);
 
   void acceptDefaultAsCompanionMethod(ProgramMethod method, ProgramMethod companionMethod);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
index cea8eab..587dafa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
@@ -24,7 +24,7 @@
     private EmptyInterfaceMethodDesugaringEventConsumer() {}
 
     @Override
-    public void acceptCompanionClassClinit(ProgramMethod method) {
+    public void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod) {
       // Intentionally empty.
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
index 0c1c56f..1653ae2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -112,7 +112,7 @@
       intConstantI.setPosition(constantPosition);
       iterator.add(intConstantI);
       ArrayPut arrayPut =
-          new ArrayPut(
+          ArrayPut.create(
               MemberType.OBJECT,
               newArrayEmpty.outValue(),
               intConstantI.outValue(),
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 9f985e9..692eeee 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
@@ -3567,8 +3567,8 @@
                   phiBlock.getInstructions().add(insertIndex++, cstToUse);
                 }
                 phi.replaceUsers(newOutValue);
-                Instruction newInstruction = new Xor(NumericType.INT, newOutValue, testValue,
-                    cstToUse.outValue());
+                Instruction newInstruction =
+                    Xor.create(NumericType.INT, newOutValue, testValue, cstToUse.outValue());
                 newInstruction.setBlock(phiBlock);
                 // The xor is replacing a phi so it does not have an actual position.
                 newInstruction.setPosition(phiPosition);
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 ee9878a..12e1f1e 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
@@ -1333,76 +1333,102 @@
       }
       return Reason.INVALID_INVOKE_ON_ARRAY;
     }
-    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
-    if (singleTarget == null) {
+
+    DexClassAndMethod resolvedMethod =
+        appView
+            .appInfo()
+            .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+            .getResolutionPair();
+    if (resolvedMethod == null) {
       return Reason.INVALID_INVOKE;
     }
-    DexMethod singleTargetReference = singleTarget.getReference();
-    DexClass targetHolder = singleTarget.getHolder();
-    if (targetHolder.isProgramClass()) {
-      if (targetHolder.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) {
+    // The single target may be null if for example this is a virtual invoke into an abstract
+    // method.
+    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+    DexClassAndMethod mostAccurateTarget = singleTarget == null ? resolvedMethod : singleTarget;
+
+    if (mostAccurateTarget.isProgramMethod()) {
+      if (mostAccurateTarget.getHolder().isEnum()
+          && resolvedMethod.getDefinition().isInstanceInitializer()) {
         // The enum instance initializer is only allowed to be called from an initializer of the
         // enum itself.
-        if (getEnumUnboxingCandidateOrNull(code.context().getHolder().getType())
-                != getEnumUnboxingCandidateOrNull(targetHolder.getType())
+        if (getEnumUnboxingCandidateOrNull(code.context().getHolderType())
+                != getEnumUnboxingCandidateOrNull(mostAccurateTarget.getHolderType())
             || !context.getDefinition().isInitializer()) {
           return Reason.INVALID_INIT;
         }
-        if (code.method().isInstanceInitializer() && !invoke.getFirstArgument().isThis()) {
+        if (context.getDefinition().isInstanceInitializer()
+            && !invoke.getFirstArgument().isThis()) {
           return Reason.INVALID_INIT;
         }
       }
 
       // Check if this is a checkNotNull() user. In this case, we can create a copy of the method
       // that takes an int instead of java.lang.Object and call that method instead.
-      EnumUnboxerMethodClassification classification =
-          singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
-      if (classification.isCheckNotNullClassification()) {
-        CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
-            classification.asCheckNotNullClassification();
-        if (checkNotNullClassification.isUseEligibleForUnboxing(
-            invoke.asInvokeStatic(), enumValue)) {
-          GraphLens graphLens = appView.graphLens();
-          checkNotNullMethodsBuilder
-              .computeIfAbsent(
-                  singleTarget.asProgramMethod(),
-                  ignoreKey(
-                      () ->
-                          LongLivedClassSetBuilder.createConcurrentBuilderForIdentitySet(
-                              graphLens)),
-                  graphLens)
-              .add(enumClass, graphLens);
-          return Reason.ELIGIBLE;
+      if (singleTarget != null) {
+        EnumUnboxerMethodClassification classification =
+            singleTarget.getOptimizationInfo().getEnumUnboxerMethodClassification();
+        if (classification.isCheckNotNullClassification()) {
+          assert singleTarget.getDefinition().isStatic();
+          CheckNotNullEnumUnboxerMethodClassification checkNotNullClassification =
+              classification.asCheckNotNullClassification();
+          if (checkNotNullClassification.isUseEligibleForUnboxing(
+              invoke.asInvokeStatic(), enumValue)) {
+            GraphLens graphLens = appView.graphLens();
+            checkNotNullMethodsBuilder
+                .computeIfAbsent(
+                    singleTarget.asProgramMethod(),
+                    ignoreKey(
+                        () ->
+                            LongLivedClassSetBuilder.createConcurrentBuilderForIdentitySet(
+                                graphLens)),
+                    graphLens)
+                .add(enumClass, graphLens);
+            return Reason.ELIGIBLE;
+          }
         }
       }
 
       // Check that the enum-value only flows into parameters whose type exactly matches the
       // enum's type.
-      for (int i = 0; i < singleTarget.getParameters().size(); i++) {
+      for (int i = 0; i < mostAccurateTarget.getParameters().size(); i++) {
         if (invoke.getArgumentForParameter(i) == enumValue
             && !enumUnboxingCandidatesInfo.isAssignableTo(
-                singleTarget.getParameter(i).toBaseType(factory), enumClass.getType())) {
-          return new IllegalInvokeWithImpreciseParameterTypeReason(singleTargetReference);
+                mostAccurateTarget.getParameter(i).toBaseType(factory), enumClass.getType())) {
+          return new IllegalInvokeWithImpreciseParameterTypeReason(
+              mostAccurateTarget.getReference());
         }
       }
       if (invoke.isInvokeMethodWithReceiver()) {
         Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
-        if (receiver == enumValue && targetHolder.isInterface()) {
+        if (receiver == enumValue && mostAccurateTarget.getHolder().isInterface()) {
           return Reason.DEFAULT_METHOD_INVOKE;
+
         }
       }
       return Reason.ELIGIBLE;
     }
 
-    if (targetHolder.isClasspathClass()) {
+    if (mostAccurateTarget.getHolder().isClasspathClass()) {
       return Reason.INVALID_INVOKE_CLASSPATH;
     }
 
-    assert targetHolder.isLibraryClass();
+    assert mostAccurateTarget.getHolder().isLibraryClass();
+
+    if (singleTarget == null) {
+      // We don't attempt library modeling if we don't have a single target.
+      return Reason.INVALID_INVOKE;
+    }
 
     Reason reason =
         analyzeLibraryInvoke(
-            invoke, code, context, enumClass, enumValue, singleTargetReference, targetHolder);
+            invoke,
+            code,
+            context,
+            enumClass,
+            enumValue,
+            singleTarget.getReference(),
+            singleTarget.getHolder());
 
     if (reason == Reason.ELIGIBLE) {
       markMethodDependsOnLibraryModelisation(context);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index da8ac44..3aaff49 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -201,7 +201,7 @@
         newMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
     private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
 
-    private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
+    private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
         new IdentityHashMap<>();
 
     Builder(AppView<AppInfoWithLiveness> appView) {
@@ -219,41 +219,31 @@
       if (from == to) {
         return;
       }
-      newFieldSignatures.put(from, to);
-    }
-
-    private RewrittenPrototypeDescription recordPrototypeChanges(
-        DexMethod from,
-        DexMethod to,
-        boolean fromStatic,
-        boolean toStatic,
-        boolean virtualReceiverAlreadyRemapped,
-        List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
-      assert from != to;
-      RewrittenPrototypeDescription prototypeChanges =
-          computePrototypeChanges(
-              from,
-              to,
-              fromStatic,
-              toStatic,
-              virtualReceiverAlreadyRemapped,
-              extraUnusedNullParameters);
-      prototypeChangesPerMethod.put(to, prototypeChanges);
-      return prototypeChanges;
+      synchronized (this) {
+        newFieldSignatures.put(from, to);
+      }
     }
 
     public void moveAndMap(DexMethod from, DexMethod to, boolean fromStatic) {
       moveAndMap(from, to, fromStatic, true, Collections.emptyList());
     }
 
-    public RewrittenPrototypeDescription moveVirtual(DexMethod from, DexMethod to) {
-      newMethodSignatures.put(from, to);
-      return recordPrototypeChanges(from, to, false, true, false, Collections.emptyList());
+    public void moveVirtual(DexMethod from, DexMethod to) {
+      RewrittenPrototypeDescription prototypeChanges =
+          computePrototypeChanges(from, to, false, true, false, Collections.emptyList());
+      synchronized (this) {
+        newMethodSignatures.put(from, to);
+        prototypeChangesPerMethod.put(to, prototypeChanges);
+      }
     }
 
-    public RewrittenPrototypeDescription mapToDispatch(DexMethod from, DexMethod to) {
-      methodMap.put(from, to);
-      return recordPrototypeChanges(from, to, false, true, true, Collections.emptyList());
+    public void mapToDispatch(DexMethod from, DexMethod to) {
+      RewrittenPrototypeDescription prototypeChanges =
+          computePrototypeChanges(from, to, false, true, true, Collections.emptyList());
+      synchronized (this) {
+        methodMap.put(from, to);
+        prototypeChangesPerMethod.put(to, prototypeChanges);
+      }
     }
 
     public RewrittenPrototypeDescription moveAndMap(
@@ -262,10 +252,14 @@
         boolean fromStatic,
         boolean toStatic,
         List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
-      newMethodSignatures.put(from, to);
-      methodMap.put(from, to);
-      return recordPrototypeChanges(
-          from, to, fromStatic, toStatic, false, extraUnusedNullParameters);
+      RewrittenPrototypeDescription prototypeChanges =
+          computePrototypeChanges(from, to, fromStatic, toStatic, false, extraUnusedNullParameters);
+      synchronized (this) {
+        newMethodSignatures.put(from, to);
+        methodMap.put(from, to);
+        prototypeChangesPerMethod.put(to, prototypeChanges);
+      }
+      return prototypeChanges;
     }
 
     private RewrittenPrototypeDescription computePrototypeChanges(
@@ -275,6 +269,7 @@
         boolean toStatic,
         boolean virtualReceiverAlreadyRemapped,
         List<ExtraUnusedNullParameter> extraUnusedNullParameters) {
+      assert from != to;
       int offsetDiff = 0;
       int toOffset = BooleanUtils.intValue(!toStatic);
       ArgumentInfoCollection.Builder builder =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 8d04a32..07a54e2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -9,7 +9,6 @@
 
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedField.Builder;
@@ -26,6 +25,9 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup;
+import com.android.tools.r8.graph.fixup.ConcurrentMethodFixup.ProgramClassFixer;
+import com.android.tools.r8.graph.fixup.MethodNamingUtility;
 import com.android.tools.r8.graph.lens.MethodLookupResult;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -66,10 +68,11 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -81,7 +84,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.function.Predicate;
 
-class EnumUnboxingTreeFixer {
+class EnumUnboxingTreeFixer implements ProgramClassFixer {
 
   private final EnumUnboxingLens.Builder lensBuilder;
   private final AppView<AppInfoWithLiveness> appView;
@@ -94,7 +97,9 @@
   // we duplicate that here as DexProgramClasses.
   private final Map<DexProgramClass, Set<DexProgramClass>> unboxedEnumHierarchy;
   private final EnumUnboxingUtilityClasses utilityClasses;
-  private final ProgramMethodMap<CfCodeWithLens> dispatchMethods = ProgramMethodMap.create();
+  private final ProgramMethodMap<CfCodeWithLens> dispatchMethods =
+      ProgramMethodMap.createConcurrent();
+  private final PrunedItems.Builder prunedItemsBuilder;
 
   EnumUnboxingTreeFixer(
       AppView<AppInfoWithLiveness> appView,
@@ -110,6 +115,7 @@
     this.lensBuilder =
         EnumUnboxingLens.enumUnboxingLensBuilder(appView).mapUnboxedEnums(getUnboxedEnums());
     this.utilityClasses = utilityClasses;
+    this.prunedItemsBuilder = PrunedItems.concurrentBuilder();
   }
 
   private Set<DexProgramClass> computeUnboxedEnumClasses() {
@@ -128,39 +134,13 @@
 
   Result fixupTypeReferences(IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
-    PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder();
 
     // We do this before so that we can still perform lookup of definitions.
     fixupSuperEnumClassInitializers(converter, executorService);
 
     // Fix all methods and fields using enums to unbox.
-    // TODO(b/191617665): Parallelize this fixup.
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (enumDataMap.isSuperUnboxedEnum(clazz.getType())) {
-
-        // Clear the initializers and move the other methods to the new location.
-        LocalEnumUnboxingUtilityClass localUtilityClass =
-            utilityClasses.getLocalUtilityClass(clazz);
-        Collection<DexEncodedField> localUtilityFields =
-            createLocalUtilityFields(clazz, localUtilityClass, prunedItemsBuilder);
-        Collection<DexEncodedMethod> localUtilityMethods =
-            createLocalUtilityMethods(
-                clazz, unboxedEnumHierarchy.get(clazz), localUtilityClass, prunedItemsBuilder);
-
-        // Cleanup old classes.
-        cleanUpOldClass(clazz);
-        for (DexProgramClass subEnum : unboxedEnumHierarchy.get(clazz)) {
-          cleanUpOldClass(subEnum);
-        }
-
-        // Update members on the local utility class.
-        localUtilityClass.getDefinition().setDirectMethods(localUtilityMethods);
-        localUtilityClass.getDefinition().setStaticFields(localUtilityFields);
-      } else if (!enumDataMap.isUnboxedEnum(clazz.getType())) {
-        clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
-        clazz.getFieldCollection().replaceFields(this::fixupEncodedField);
-      }
-    }
+    new ConcurrentMethodFixup(appView, this)
+        .fixupClassesConcurrentlyByConnectedProgramComponents(Timing.empty(), executorService);
 
     // Install the new graph lens before processing any checkNotZero() methods.
     EnumUnboxingLens lens = lensBuilder.build(appView);
@@ -191,6 +171,38 @@
     clazz.getMethodCollection().clearVirtualMethods();
   }
 
+  @Override
+  public boolean shouldReserveAsIfPinned(ProgramMethod method) {
+    DexProto oldProto = method.getProto();
+    DexProto newProto = fixupProto(oldProto);
+    // We don't track nor reprocess dependencies of unchanged methods so we have to maintain them
+    // with the same signature.
+    return oldProto == newProto;
+  }
+
+  @Override
+  public void fixupProgramClass(DexProgramClass clazz, MethodNamingUtility utility) {
+    if (enumDataMap.isSuperUnboxedEnum(clazz.getType())) {
+      // Clear the initializers and move the other methods to the new location.
+      LocalEnumUnboxingUtilityClass localUtilityClass = utilityClasses.getLocalUtilityClass(clazz);
+      Collection<DexEncodedField> localUtilityFields =
+          createLocalUtilityFields(clazz, localUtilityClass);
+      Collection<DexEncodedMethod> localUtilityMethods =
+          createLocalUtilityMethods(clazz, unboxedEnumHierarchy.get(clazz), localUtilityClass);
+      // Cleanup old classes.
+      cleanUpOldClass(clazz);
+      for (DexProgramClass subEnum : unboxedEnumHierarchy.get(clazz)) {
+        cleanUpOldClass(subEnum);
+      }
+      // Update members on the local utility class.
+      localUtilityClass.getDefinition().setDirectMethods(localUtilityMethods);
+      localUtilityClass.getDefinition().setStaticFields(localUtilityFields);
+    } else if (!enumDataMap.isUnboxedEnum(clazz.getType())) {
+      clazz.getMethodCollection().replaceMethods(m -> fixupEncodedMethod(m, utility));
+      clazz.getFieldCollection().replaceFields(this::fixupEncodedField);
+    }
+  }
+
   private BiMap<DexMethod, DexMethod> duplicateCheckNotNullMethods(
       IRConverter converter, ExecutorService executorService) throws ExecutionException {
     BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping = HashBiMap.create();
@@ -397,8 +409,11 @@
               instructionsToRemove.put(constructorInvoke, Optional.empty());
             }
 
-            ProgramMethod constructor =
-                unboxedEnum.lookupProgramMethod(lookupResult.getReference());
+            DexProgramClass holder =
+                newInstance.getType() == unboxedEnum.getType()
+                    ? unboxedEnum
+                    : appView.programDefinitionFor(newInstance.getType(), classInitializer);
+            ProgramMethod constructor = holder.lookupProgramMethod(lookupResult.getReference());
             assert constructor != null;
 
             InstanceFieldInitializationInfo ordinalInitializationInfo =
@@ -473,9 +488,7 @@
   }
 
   private Collection<DexEncodedField> createLocalUtilityFields(
-      DexProgramClass unboxedEnum,
-      LocalEnumUnboxingUtilityClass localUtilityClass,
-      PrunedItems.Builder prunedItemsBuilder) {
+      DexProgramClass unboxedEnum, LocalEnumUnboxingUtilityClass localUtilityClass) {
     EnumData enumData = enumDataMap.get(unboxedEnum);
     Map<DexField, DexEncodedField> localUtilityFields =
         new LinkedHashMap<>(unboxedEnum.staticFields().size());
@@ -532,7 +545,6 @@
 
   private void processMethod(
       ProgramMethod method,
-      PrunedItems.Builder prunedItemsBuilder,
       DexMethodSignatureSet nonPrivateVirtualMethods,
       LocalEnumUnboxingUtilityClass localUtilityClass,
       Map<DexMethod, DexEncodedMethod> localUtilityMethods) {
@@ -552,8 +564,7 @@
   private Collection<DexEncodedMethod> createLocalUtilityMethods(
       DexProgramClass unboxedEnum,
       Set<DexProgramClass> subEnums,
-      LocalEnumUnboxingUtilityClass localUtilityClass,
-      PrunedItems.Builder prunedItemsBuilder) {
+      LocalEnumUnboxingUtilityClass localUtilityClass) {
     Map<DexMethod, DexEncodedMethod> localUtilityMethods =
         new LinkedHashMap<>(
             localUtilityClass.getDefinition().getMethodCollection().size()
@@ -568,7 +579,6 @@
         method ->
             processMethod(
                 method,
-                prunedItemsBuilder,
                 nonPrivateVirtualMethods,
                 localUtilityClass,
                 localUtilityMethods));
@@ -578,7 +588,6 @@
           method ->
               processMethod(
                   method,
-                  prunedItemsBuilder,
                   nonPrivateVirtualMethods,
                   localUtilityClass,
                   localUtilityMethods));
@@ -617,7 +626,7 @@
     }
     if (superMethod == null || subimplementations.isEmpty()) {
       // No emulated dispatch is required, just move everything.
-      if (superMethod != null) {
+      if (superMethod != null && !superMethod.getAccessFlags().isAbstract()) {
         assert superMethod.isProgramMethod();
         directMoveAndMap(localUtilityClass, localUtilityMethods, superMethod.asProgramMethod());
       }
@@ -635,14 +644,13 @@
       LocalEnumUnboxingUtilityClass localUtilityClass,
       Map<DexMethod, DexEncodedMethod> localUtilityMethods,
       DexClassAndMethod superMethod,
-      ProgramMethodSet subimplementations) {
-    assert !subimplementations.isEmpty();
+      ProgramMethodSet unorderedSubimplementations) {
+    assert !unorderedSubimplementations.isEmpty();
     DexMethod superUtilityMethod;
     if (superMethod.isProgramMethod()) {
       superUtilityMethod =
           installLocalUtilityMethod(
-                  localUtilityClass, localUtilityMethods, superMethod.asProgramMethod())
-              .getReference();
+              localUtilityClass, localUtilityMethods, superMethod.asProgramMethod());
     } else {
       // All methods but toString() are final or non-virtual.
       // We could support other cases by setting correctly the superUtilityMethod here.
@@ -650,17 +658,19 @@
       superUtilityMethod = localUtilityClass.computeToStringUtilityMethod(factory);
     }
     Map<DexMethod, DexMethod> overrideToUtilityMethods = new IdentityHashMap<>();
-    for (ProgramMethod subMethod : subimplementations) {
-      DexEncodedMethod subEnumLocalUtilityMethod =
+    List<ProgramMethod> sortedSubimplementations = new ArrayList<>(unorderedSubimplementations);
+    sortedSubimplementations.sort(Comparator.comparing(ProgramMethod::getHolderType));
+    for (ProgramMethod subMethod : sortedSubimplementations) {
+      DexMethod subEnumLocalUtilityMethod =
           installLocalUtilityMethod(localUtilityClass, localUtilityMethods, subMethod);
-      overrideToUtilityMethods.put(
-          subMethod.getReference(), subEnumLocalUtilityMethod.getReference());
+      assert subEnumLocalUtilityMethod != null;
+      overrideToUtilityMethods.put(subMethod.getReference(), subEnumLocalUtilityMethod);
     }
     DexMethod dispatch =
         installDispatchMethod(
                 localUtilityClass,
                 localUtilityMethods,
-                subimplementations.iterator().next(),
+                sortedSubimplementations.iterator().next(),
                 superUtilityMethod,
                 overrideToUtilityMethods)
             .getReference();
@@ -682,16 +692,21 @@
       LocalEnumUnboxingUtilityClass localUtilityClass,
       Map<DexMethod, DexEncodedMethod> localUtilityMethods,
       ProgramMethod method) {
-    DexEncodedMethod utilityMethod =
+    assert !method.getAccessFlags().isAbstract();
+    DexMethod utilityMethod =
         installLocalUtilityMethod(localUtilityClass, localUtilityMethods, method);
-    lensBuilder.moveAndMap(
-        method.getReference(), utilityMethod.getReference(), method.getDefinition().isStatic());
+    assert utilityMethod != null;
+    lensBuilder.moveAndMap(method.getReference(), utilityMethod, method.getDefinition().isStatic());
   }
 
   public void recordEmulatedDispatch(DexMethod from, DexMethod move, DexMethod dispatch) {
     // Move is used for getRenamedSignature and to remap invoke-super.
     // Map is used to remap all the other invokes.
-    lensBuilder.moveVirtual(from, move);
+    assert from != null;
+    assert dispatch != null;
+    if (move != null) {
+      lensBuilder.moveVirtual(from, move);
+    }
     lensBuilder.mapToDispatch(from, dispatch);
   }
 
@@ -708,7 +723,7 @@
             fixupProto(factory.prependHolderToProto(representative.getReference())),
             localUtilityClass.getType(),
             newMethodSignature -> !localUtilityMethods.containsKey(newMethodSignature));
-    Int2ObjectMap<DexMethod> methodMap = new Int2ObjectArrayMap<>();
+    Int2ObjectSortedMap<DexMethod> methodMap = new Int2ObjectLinkedOpenHashMap<>();
     IdentityHashMap<DexType, DexMethod> typeToMethod = new IdentityHashMap<>();
     map.forEach(
         (methodReference, newMethodReference) ->
@@ -744,10 +759,13 @@
     return newLocalUtilityMethod;
   }
 
-  private DexEncodedMethod installLocalUtilityMethod(
+  private DexMethod installLocalUtilityMethod(
       LocalEnumUnboxingUtilityClass localUtilityClass,
       Map<DexMethod, DexEncodedMethod> localUtilityMethods,
       ProgramMethod method) {
+    if (method.getAccessFlags().isAbstract()) {
+      return null;
+    }
     DexEncodedMethod newLocalUtilityMethod =
         createLocalUtilityMethod(
             method,
@@ -755,7 +773,7 @@
             newMethodSignature -> !localUtilityMethods.containsKey(newMethodSignature));
     assert !localUtilityMethods.containsKey(newLocalUtilityMethod.getReference());
     localUtilityMethods.put(newLocalUtilityMethod.getReference(), newLocalUtilityMethod);
-    return newLocalUtilityMethod;
+    return newLocalUtilityMethod.getReference();
   }
 
   private DexEncodedMethod createLocalUtilityMethod(
@@ -804,20 +822,25 @@
             && !field.getDefinition().getOptimizationInfo().isDead());
   }
 
-  private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
+  private DexEncodedMethod fixupEncodedMethod(
+      DexEncodedMethod method, MethodNamingUtility utility) {
     DexProto oldProto = method.getProto();
     DexProto newProto = fixupProto(oldProto);
-    if (newProto == method.getProto()) {
+    if (oldProto == newProto) {
+      assert method.getReference()
+          == utility.nextUniqueMethod(
+              method, newProto, utilityClasses.getSharedUtilityClass().getType());
       return method;
     }
+
+    DexMethod newMethod =
+        utility.nextUniqueMethod(
+            method, newProto, utilityClasses.getSharedUtilityClass().getType());
+    assert newMethod != method.getReference();
     assert !method.isClassInitializer();
     assert !method.isLibraryMethodOverride().isTrue()
         : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
-    // We add the $enumunboxing$ suffix to make sure we do not create a library override.
-    String newMethodName =
-        method.getName().toString() + (method.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
-    DexMethod newMethod = factory.createMethod(method.getHolderType(), newProto, newMethodName);
-    newMethod = ensureUniqueMethod(method, newMethod);
+
     List<ExtraUnusedNullParameter> extraUnusedNullParameters =
         ExtraUnusedNullParameter.computeExtraUnusedNullParameters(method.getReference(), newMethod);
     boolean isStatic = method.isStatic();
@@ -835,27 +858,6 @@
                     method.isNonPrivateVirtualMethod(), OptionalBool.FALSE));
   }
 
-  private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
-    DexClass holder = appView.definitionFor(encodedMethod.getHolderType());
-    assert holder != null;
-    if (newMethod.isInstanceInitializer(appView.dexItemFactory())) {
-      newMethod =
-          factory.createInstanceInitializerWithFreshProto(
-              newMethod,
-              utilityClasses.getSharedUtilityClass().getType(),
-              tryMethod -> holder.lookupMethod(tryMethod) == null);
-    } else {
-      int index = 0;
-      while (holder.lookupMethod(newMethod) != null) {
-        newMethod =
-            newMethod.withName(
-                encodedMethod.getName().toString() + "$enumunboxing$" + index++,
-                appView.dexItemFactory());
-      }
-    }
-    return newMethod;
-  }
-
   private DexEncodedField fixupEncodedField(DexEncodedField encodedField) {
     DexField field = encodedField.getReference();
     DexType newType = fixupType(field.type);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
index ef163ad..3b0caa2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlinerImpl.java
@@ -300,10 +300,10 @@
       Instruction newInstruction = null;
       switch (type) {
         case ADD:
-          newInstruction = new Add(numericType, outValue, inValues.get(0), inValues.get(1));
+          newInstruction = Add.create(numericType, outValue, inValues.get(0), inValues.get(1));
           break;
         case MUL:
-          newInstruction = new Mul(numericType, outValue, inValues.get(0), inValues.get(1));
+          newInstruction = Mul.create(numericType, outValue, inValues.get(0), inValues.get(1));
           break;
         case SUB:
           newInstruction = new Sub(numericType, outValue, inValues.get(0), inValues.get(1));
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index af9e41c..755f207 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -36,7 +36,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -72,13 +72,13 @@
 
     private final GraphLens codeLens;
     private final DexMethod superEnumMethod;
-    private final Int2ObjectMap<DexMethod> methodMap;
+    private final Int2ObjectSortedMap<DexMethod> methodMap;
 
     public EnumUnboxingMethodDispatchCfCodeProvider(
         AppView<?> appView,
         DexType holder,
         DexMethod superEnumMethod,
-        Int2ObjectMap<DexMethod> methodMap) {
+        Int2ObjectSortedMap<DexMethod> methodMap) {
       super(appView, holder);
       this.codeLens = appView.codeLens();
       this.superEnumMethod = superEnumMethod;
@@ -90,27 +90,42 @@
       // TODO(b/167942775): Should use a table-switch for large enums (maybe same threshold in the
       //  rewriter of switchmaps).
 
+      assert !methodMap.isEmpty();
       DexItemFactory factory = appView.dexItemFactory();
-      int returnInvokeSize = superEnumMethod.getParameters().size() + 2;
-      List<CfInstruction> instructions =
-          new ArrayList<>(methodMap.size() * (returnInvokeSize + 5) + returnInvokeSize);
+      boolean hasDefaultCase = superEnumMethod != null;
+      DexMethod representative = methodMap.values().iterator().next();
+
+      int invokeSize = representative.getParameters().size() + 2;
+      int branchSize = 5;
+      int instructionsSize =
+          methodMap.size() * (invokeSize + branchSize)
+              + (hasDefaultCase ? invokeSize : -branchSize);
+      List<CfInstruction> instructions = new ArrayList<>(instructionsSize);
 
       CfFrame.Builder frameBuilder = CfFrame.builder();
-      for (DexType parameter : superEnumMethod.getParameters()) {
+      for (DexType parameter : representative.getParameters()) {
         frameBuilder.appendLocal(FrameType.initialized(parameter));
       }
       methodMap.forEach(
           (unboxedEnumValue, method) -> {
-            CfLabel dest = new CfLabel();
-            instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
-            instructions.add(new CfConstNumber(unboxedEnumValue, ValueType.INT));
-            instructions.add(new CfIfCmp(IfType.NE, ValueType.INT, dest));
-            addReturnInvoke(instructions, method);
-            instructions.add(dest);
-            instructions.add(frameBuilder.build());
+            boolean lastCase = methodMap.lastIntKey() == unboxedEnumValue && !hasDefaultCase;
+            if (!lastCase) {
+              CfLabel dest = new CfLabel();
+              instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
+              instructions.add(new CfConstNumber(unboxedEnumValue, ValueType.INT));
+              instructions.add(new CfIfCmp(IfType.NE, ValueType.INT, dest));
+              addReturnInvoke(instructions, method);
+              instructions.add(dest);
+              instructions.add(frameBuilder.build());
+            } else {
+              addReturnInvoke(instructions, method);
+            }
           });
 
-      addReturnInvoke(instructions, superEnumMethod);
+      if (hasDefaultCase) {
+        addReturnInvoke(instructions, superEnumMethod);
+      }
+      assert instructions.size() == instructionsSize;
       return new CfCodeWithLens(getHolder(), defaultMaxStack(), defaultMaxLocals(), instructions);
     }
 
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 5c92ac9..19febfd 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -112,7 +112,7 @@
   }
 
   public void write(ClassFileConsumer consumer) {
-    assert options.proguardMapConsumer == null;
+    assert options.mapConsumer == null;
     write(consumer, null);
   }
 
@@ -138,7 +138,7 @@
 
   private void writeApplication(AndroidApp inputApp, ClassFileConsumer consumer) {
     ProguardMapId proguardMapId = null;
-    if (options.proguardMapConsumer != null) {
+    if (options.mapConsumer != null) {
       assert marker.isPresent();
       proguardMapId =
           runAndWriteMap(
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 70c442b..6d6d497 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
@@ -11,12 +12,16 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.ArrayLength;
+import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -36,6 +41,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.MonitorType;
 import com.android.tools.r8.ir.code.MoveException;
@@ -54,9 +60,12 @@
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.lightir.LirCode.PositionEntry;
 import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -202,8 +211,7 @@
         // LIR has no value-user info so after building is done, removed unused values.
         for (Instruction instruction : block.getInstructions()) {
           if (instruction.hasOutValue()
-              && !instruction.isArgument()
-              && !instruction.isMoveException()
+              && instruction.isInvoke()
               && instruction.hasUnusedOutValue()) {
             instruction.clearOutValue();
           }
@@ -330,6 +338,11 @@
     }
 
     @Override
+    public void onInstruction() {
+      throw new Unimplemented("Missing IR conversion");
+    }
+
+    @Override
     public void onConstNull() {
       Value dest = getOutValueForNextInstruction(TypeElement.getNull());
       addInstruction(new ConstNumber(dest, 0));
@@ -342,36 +355,66 @@
     }
 
     @Override
+    public void onConstFloat(int value) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getFloat());
+      addInstruction(new ConstNumber(dest, value));
+    }
+
+    @Override
+    public void onConstLong(long value) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getLong());
+      addInstruction(new ConstNumber(dest, value));
+    }
+
+    @Override
+    public void onConstDouble(long value) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getDouble());
+      addInstruction(new ConstNumber(dest, value));
+    }
+
+    TypeElement valueTypeElement(NumericType type) {
+      return PrimitiveTypeElement.fromNumericType(type);
+    }
+
+    @Override
     public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
-      addInstruction(new Add(type, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(
+          Add.createNonNormalized(type, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
     }
 
     @Override
     public void onSub(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
       addInstruction(new Sub(type, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
     }
 
     @Override
     public void onMul(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
-      addInstruction(new Mul(type, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(
+          Mul.createNonNormalized(type, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
     }
 
     @Override
     public void onDiv(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
       addInstruction(new Div(type, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
     }
 
     @Override
     public void onRem(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
       addInstruction(new Rem(type, dest, getValue(leftValueIndex), getValue(rightValueIndex)));
     }
 
     @Override
+    public void onXor(NumericType type, EV left, EV right) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(Xor.createNonNormalized(type, dest, getValue(left), getValue(right)));
+    }
+
+    @Override
     public void onConstString(DexString string) {
       Value dest = getOutValueForNextInstruction(TypeElement.stringClassType(appView));
       addInstruction(new ConstString(dest, string));
@@ -396,6 +439,17 @@
     }
 
     @Override
+    public void onIfCmp(IfType ifKind, int blockIndex, EV leftValueIndex, EV rightValueIndex) {
+      BasicBlock targetBlock = getBasicBlock(blockIndex);
+      Value leftValue = getValue(leftValueIndex);
+      Value rightValue = getValue(rightValueIndex);
+      addInstruction(new If(ifKind, ImmutableList.of(leftValue, rightValue)));
+      currentBlock.link(targetBlock);
+      currentBlock.link(getBasicBlock(nextInstructionIndex));
+      closeCurrentBlock();
+    }
+
+    @Override
     public void onFallthrough() {
       int nextBlockIndex = peekNextInstructionIndex() + 1;
       onGoto(nextBlockIndex);
@@ -498,6 +552,12 @@
     }
 
     @Override
+    public void onReturn(EV value) {
+      addInstruction(new Return(getValue(value)));
+      closeCurrentBlock();
+    }
+
+    @Override
     public void onArrayLength(EV arrayValueIndex) {
       Value dest = getOutValueForNextInstruction(TypeElement.getInt());
       Value arrayValue = getValue(arrayValueIndex);
@@ -505,6 +565,12 @@
     }
 
     @Override
+    public void onCheckCast(DexType type, EV value) {
+      Value dest = getOutValueForNextInstruction(type.toTypeElement(appView));
+      addInstruction(new CheckCast(dest, getValue(value), type));
+    }
+
+    @Override
     public void onDebugPosition() {
       addInstruction(new DebugPosition());
     }
@@ -578,5 +644,27 @@
     public void onMonitorExit(EV value) {
       addInstruction(new Monitor(MonitorType.EXIT, getValue(value)));
     }
+
+    @Override
+    public void onArrayGetObject(DexType type, EV array, EV index) {
+      Value dest = getOutValueForNextInstruction(type.toTypeElement(appView));
+      addInstruction(new ArrayGet(MemberType.OBJECT, dest, getValue(array), getValue(index)));
+    }
+
+    @Override
+    public void onArrayGetPrimitive(MemberType type, EV array, EV index) {
+      // Convert the member type to a "stack value type", e.g., byte, char etc to int.
+      ValueType valueType = ValueType.fromMemberType(type);
+      DexType dexType = valueType.toDexType(appView.dexItemFactory());
+      Value dest = getOutValueForNextInstruction(dexType.toTypeElement(appView));
+      addInstruction(new ArrayGet(type, dest, getValue(array), getValue(index)));
+    }
+
+    @Override
+    public void onArrayPut(MemberType type, EV array, EV index, EV value) {
+      addInstruction(
+          ArrayPut.createWithoutVerification(
+              type, getValue(array), getValue(index), getValue(value)));
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index 91ce188..8f41773 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.MonitorType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
@@ -88,6 +90,10 @@
     flushedPosition = currentPosition;
   }
 
+  public DexItemFactory factory() {
+    return factory;
+  }
+
   public boolean verifyCurrentValueIndex(int valueIndex) {
     assert instructionCount + argumentCount == valueIndex;
     return true;
@@ -109,7 +115,6 @@
 
   public LirBuilder<V, EV> setCurrentPosition(Position position) {
     assert position != null;
-    assert position != Position.none();
     currentPosition = position;
     return this;
   }
@@ -363,6 +368,11 @@
     return addOneValueInstruction(LirOpcodes.ARRAYLENGTH, array);
   }
 
+  public LirBuilder<V, EV> addCheckCast(DexType type, V value) {
+    return addInstructionTemplate(
+        LirOpcodes.CHECKCAST, Collections.singletonList(type), Collections.singletonList(value));
+  }
+
   public LirBuilder<V, EV> addStaticGet(DexField field) {
     return addOneItemInstruction(LirOpcodes.GETSTATIC, field);
   }
@@ -566,7 +576,7 @@
   }
 
   public LirBuilder<V, EV> addArithmeticBinop(
-      Opcode binop, NumericType type, V leftValue, V rightValue) {
+      CfArithmeticBinop.Opcode binop, NumericType type, V leftValue, V rightValue) {
     // The LIR and CF opcodes are the same values, check that the two endpoints match.
     assert LirOpcodes.IADD == CfArithmeticBinop.getAsmOpcode(Opcode.Add, NumericType.INT);
     assert LirOpcodes.DREM == CfArithmeticBinop.getAsmOpcode(Opcode.Rem, NumericType.DOUBLE);
@@ -574,6 +584,17 @@
     return addTwoValueInstruction(opcode, leftValue, rightValue);
   }
 
+  public LirBuilder<V, EV> addLogicalBinop(
+      CfLogicalBinop.Opcode binop, NumericType type, V leftValue, V rightValue) {
+    // The LIR and CF opcodes are the same values, check that the two endpoints match.
+    assert LirOpcodes.ISHL
+        == CfLogicalBinop.getAsmOpcode(CfLogicalBinop.Opcode.Shl, NumericType.INT);
+    assert LirOpcodes.LXOR
+        == CfLogicalBinop.getAsmOpcode(CfLogicalBinop.Opcode.Xor, NumericType.LONG);
+    int opcode = CfLogicalBinop.getAsmOpcode(binop, type);
+    return addTwoValueInstruction(opcode, leftValue, rightValue);
+  }
+
   public LirBuilder<V, EV> addMonitor(MonitorType type, V value) {
     return addOneValueInstruction(
         type == MonitorType.ENTER ? LirOpcodes.MONITORENTER : LirOpcodes.MONITOREXIT, value);
@@ -590,4 +611,73 @@
     assert opcode <= LirOpcodes.I2S;
     return addOneValueInstruction(opcode, value);
   }
+
+  public LirBuilder<V, EV> addArrayGetObject(DexType destType, V array, V index) {
+    return addInstructionTemplate(
+        LirOpcodes.AALOAD, Collections.singletonList(destType), ImmutableList.of(array, index));
+  }
+
+  public LirBuilder<V, EV> addArrayGetPrimitive(MemberType memberType, V array, V index) {
+    int opcode;
+    switch (memberType) {
+      case BOOLEAN_OR_BYTE:
+        opcode = LirOpcodes.BALOAD;
+        break;
+      case CHAR:
+        opcode = LirOpcodes.CALOAD;
+        break;
+      case SHORT:
+        opcode = LirOpcodes.SALOAD;
+        break;
+      case INT:
+        opcode = LirOpcodes.IALOAD;
+        break;
+      case FLOAT:
+        opcode = LirOpcodes.FALOAD;
+        break;
+      case LONG:
+        opcode = LirOpcodes.LALOAD;
+        break;
+      case DOUBLE:
+        opcode = LirOpcodes.DALOAD;
+        break;
+      default:
+        throw new Unreachable("Unexpected object or imprecise member type: " + memberType);
+    }
+    return addInstructionTemplate(opcode, Collections.emptyList(), ImmutableList.of(array, index));
+  }
+
+  public LirBuilder<V, EV> addArrayPut(MemberType memberType, V array, V index, V value) {
+    int opcode;
+    switch (memberType) {
+      case BOOLEAN_OR_BYTE:
+        opcode = LirOpcodes.BASTORE;
+        break;
+      case CHAR:
+        opcode = LirOpcodes.CASTORE;
+        break;
+      case SHORT:
+        opcode = LirOpcodes.SASTORE;
+        break;
+      case INT:
+        opcode = LirOpcodes.IASTORE;
+        break;
+      case FLOAT:
+        opcode = LirOpcodes.FASTORE;
+        break;
+      case LONG:
+        opcode = LirOpcodes.LASTORE;
+        break;
+      case DOUBLE:
+        opcode = LirOpcodes.DASTORE;
+        break;
+      case OBJECT:
+        opcode = LirOpcodes.AASTORE;
+        break;
+      default:
+        throw new Unreachable("Unexpected imprecise member type: " + memberType);
+    }
+    return addInstructionTemplate(
+        opcode, Collections.emptyList(), ImmutableList.of(array, index, value));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index de3a7b4..1fca6a6 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -5,12 +5,14 @@
 
 import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 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.code.IfType;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import java.util.ArrayList;
 import java.util.List;
@@ -49,6 +51,10 @@
     return getActualValueIndex(view.getNextValueOperand());
   }
 
+  private DexType getNextDexTypeOperand(LirInstructionView view) {
+    return (DexType) getConstantItem(view.getNextConstantOperand());
+  }
+
   public void onInstruction() {}
 
   public void onConstNull() {
@@ -79,6 +85,38 @@
     onInstruction();
   }
 
+  private void onArrayGetInternal(MemberType type, LirInstructionView view) {
+    if (type.isObject()) {
+      DexType destType = (DexType) getConstantItem(view.getNextConstantOperand());
+      EV array = getNextValueOperand(view);
+      EV index = getNextValueOperand(view);
+      onArrayGetObject(destType, array, index);
+    } else {
+      EV array = getNextValueOperand(view);
+      EV index = getNextValueOperand(view);
+      onArrayGetPrimitive(type, array, index);
+    }
+  }
+
+  public void onArrayGetPrimitive(MemberType type, EV array, EV index) {
+    onInstruction();
+  }
+
+  public void onArrayGetObject(DexType type, EV array, EV index) {
+    onInstruction();
+  }
+
+  private void onArrayPutInternal(MemberType type, LirInstructionView view) {
+    EV array = getNextValueOperand(view);
+    EV index = getNextValueOperand(view);
+    EV value = getNextValueOperand(view);
+    onArrayPut(type, array, index, value);
+  }
+
+  public void onArrayPut(MemberType type, EV array, EV index, EV value) {
+    onInstruction();
+  }
+
   public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
     onInstruction();
   }
@@ -179,6 +217,36 @@
     onRem(NumericType.DOUBLE, leftValueIndex, rightValueIndex);
   }
 
+  private void onLogicalBinopInternal(int opcode, LirInstructionView view) {
+    EV left = getNextValueOperand(view);
+    EV right = getNextValueOperand(view);
+    switch (opcode) {
+      case LirOpcodes.ISHL:
+      case LirOpcodes.LSHL:
+      case LirOpcodes.ISHR:
+      case LirOpcodes.LSHR:
+      case LirOpcodes.IUSHR:
+      case LirOpcodes.LUSHR:
+      case LirOpcodes.IAND:
+      case LirOpcodes.LAND:
+      case LirOpcodes.IOR:
+      case LirOpcodes.LOR:
+        throw new Unimplemented();
+      case LirOpcodes.IXOR:
+        onXor(NumericType.INT, left, right);
+        return;
+      case LirOpcodes.LXOR:
+        onXor(NumericType.INT, left, right);
+        return;
+      default:
+        throw new Unreachable("Unexpected logical binop: " + opcode);
+    }
+  }
+
+  public void onXor(NumericType type, EV left, EV right) {
+    onInstruction();
+  }
+
   public void onNumberConversion(int opcode, EV value) {
     assert LirOpcodes.I2L <= opcode;
     assert opcode <= LirOpcodes.I2S;
@@ -190,10 +258,27 @@
     onInstruction();
   }
 
+  private void onIfInternal(IfType ifKind, LirInstructionView view) {
+    int blockIndex = view.getNextBlockOperand();
+    EV valueIndex = getNextValueOperand(view);
+    onIf(ifKind, blockIndex, valueIndex);
+  }
+
   public void onIf(IfType ifKind, int blockIndex, EV valueIndex) {
     onInstruction();
   }
 
+  private void onIfCmpInternal(IfType ifKind, LirInstructionView view) {
+    int blockIndex = view.getNextBlockOperand();
+    EV leftValueIndex = getNextValueOperand(view);
+    EV rightValueIndex = getNextValueOperand(view);
+    onIfCmp(ifKind, blockIndex, leftValueIndex, rightValueIndex);
+  }
+
+  public void onIfCmp(IfType ifKind, int blockIndex, EV leftValueIndex, EV rightValueIndex) {
+    onInstruction();
+  }
+
   public void onGoto(int blockIndex) {
     onInstruction();
   }
@@ -258,10 +343,18 @@
     onInstruction();
   }
 
+  public void onReturn(EV value) {
+    onInstruction();
+  }
+
   public void onArrayLength(EV arrayValueIndex) {
     onInstruction();
   }
 
+  public void onCheckCast(DexType type, EV value) {
+    onInstruction();
+  }
+
   public void onDebugPosition() {
     onInstruction();
   }
@@ -365,6 +458,92 @@
           onConstDouble(value);
           return;
         }
+      case LirOpcodes.IALOAD:
+        {
+          onArrayGetInternal(MemberType.INT, view);
+          break;
+        }
+      case LirOpcodes.LALOAD:
+        {
+          onArrayGetInternal(MemberType.LONG, view);
+          break;
+        }
+      case LirOpcodes.FALOAD:
+        {
+          onArrayGetInternal(MemberType.FLOAT, view);
+          break;
+        }
+      case LirOpcodes.DALOAD:
+        {
+          onArrayGetInternal(MemberType.DOUBLE, view);
+          break;
+        }
+      case LirOpcodes.AALOAD:
+        {
+          onArrayGetInternal(MemberType.OBJECT, view);
+          break;
+        }
+      case LirOpcodes.BALOAD:
+        {
+          onArrayGetInternal(MemberType.BOOLEAN_OR_BYTE, view);
+          break;
+        }
+      case LirOpcodes.CALOAD:
+        {
+          onArrayGetInternal(MemberType.CHAR, view);
+          break;
+        }
+      case LirOpcodes.SALOAD:
+        {
+          onArrayGetInternal(MemberType.SHORT, view);
+          break;
+        }
+      case LirOpcodes.IASTORE:
+        {
+          onArrayPutInternal(MemberType.INT, view);
+          break;
+        }
+      case LirOpcodes.LASTORE:
+        {
+          onArrayPutInternal(MemberType.LONG, view);
+          break;
+        }
+      case LirOpcodes.FASTORE:
+        {
+          onArrayPutInternal(MemberType.FLOAT, view);
+          break;
+        }
+
+      case LirOpcodes.DASTORE:
+        {
+          onArrayPutInternal(MemberType.DOUBLE, view);
+          break;
+        }
+
+      case LirOpcodes.AASTORE:
+        {
+          onArrayPutInternal(MemberType.OBJECT, view);
+          break;
+        }
+
+      case LirOpcodes.BASTORE:
+        {
+          onArrayPutInternal(MemberType.BOOLEAN_OR_BYTE, view);
+          break;
+        }
+
+      case LirOpcodes.CASTORE:
+        {
+          onArrayPutInternal(MemberType.CHAR, view);
+          break;
+        }
+
+      case LirOpcodes.SASTORE:
+        {
+          onArrayPutInternal(MemberType.SHORT, view);
+          break;
+        }
+
       case LirOpcodes.IADD:
         {
           EV leftValueIndex = getNextValueOperand(view);
@@ -505,6 +684,22 @@
           onRemDouble(leftValueIndex, rightValueIndex);
           return;
         }
+      case LirOpcodes.ISHL:
+      case LirOpcodes.LSHL:
+      case LirOpcodes.ISHR:
+      case LirOpcodes.LSHR:
+      case LirOpcodes.IUSHR:
+      case LirOpcodes.LUSHR:
+      case LirOpcodes.IAND:
+      case LirOpcodes.LAND:
+      case LirOpcodes.IOR:
+      case LirOpcodes.LOR:
+      case LirOpcodes.IXOR:
+      case LirOpcodes.LXOR:
+        {
+          onLogicalBinopInternal(opcode, view);
+          return;
+        }
       case LirOpcodes.I2L:
       case LirOpcodes.I2F:
       case LirOpcodes.I2D:
@@ -525,11 +720,84 @@
           onNumberConversion(opcode, value);
           return;
         }
+      case LirOpcodes.IFEQ:
+        {
+          onIfInternal(IfType.EQ, view);
+          return;
+        }
       case LirOpcodes.IFNE:
         {
-          int blockIndex = view.getNextBlockOperand();
-          EV valueIndex = getNextValueOperand(view);
-          onIf(IfType.NE, blockIndex, valueIndex);
+          onIfInternal(IfType.NE, view);
+          return;
+        }
+      case LirOpcodes.IFLT:
+        {
+          onIfInternal(IfType.LT, view);
+          return;
+        }
+      case LirOpcodes.IFGE:
+        {
+          onIfInternal(IfType.GE, view);
+          return;
+        }
+      case LirOpcodes.IFGT:
+        {
+          onIfInternal(IfType.GT, view);
+          return;
+        }
+      case LirOpcodes.IFLE:
+        {
+          onIfInternal(IfType.LE, view);
+          return;
+        }
+      case LirOpcodes.IFNULL:
+        {
+          onIfInternal(IfType.EQ, view);
+          return;
+        }
+      case LirOpcodes.IFNONNULL:
+        {
+          onIfInternal(IfType.NE, view);
+          return;
+        }
+      case LirOpcodes.IF_ICMPEQ:
+        {
+          onIfCmpInternal(IfType.EQ, view);
+          return;
+        }
+      case LirOpcodes.IF_ICMPNE:
+        {
+          onIfCmpInternal(IfType.NE, view);
+          return;
+        }
+      case LirOpcodes.IF_ICMPLT:
+        {
+          onIfCmpInternal(IfType.LT, view);
+          return;
+        }
+      case LirOpcodes.IF_ICMPGE:
+        {
+          onIfCmpInternal(IfType.GE, view);
+          return;
+        }
+      case LirOpcodes.IF_ICMPGT:
+        {
+          onIfCmpInternal(IfType.GT, view);
+          return;
+        }
+      case LirOpcodes.IF_ICMPLE:
+        {
+          onIfCmpInternal(IfType.LE, view);
+          return;
+        }
+      case LirOpcodes.IF_ACMPEQ:
+        {
+          onIfCmpInternal(IfType.EQ, view);
+          return;
+        }
+      case LirOpcodes.IF_ACMPNE:
+        {
+          onIfCmpInternal(IfType.NE, view);
           return;
         }
       case LirOpcodes.GOTO:
@@ -608,7 +876,7 @@
         }
       case LirOpcodes.NEWARRAY:
         {
-          DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
+          DexType type = getNextDexTypeOperand(view);
           EV size = getNextValueOperand(view);
           onNewArrayEmpty(type, size);
           return;
@@ -624,11 +892,24 @@
           onReturnVoid();
           return;
         }
+      case LirOpcodes.ARETURN:
+        {
+          EV value = getNextValueOperand(view);
+          onReturn(value);
+          return;
+        }
       case LirOpcodes.ARRAYLENGTH:
         {
           onArrayLength(getNextValueOperand(view));
           return;
         }
+      case LirOpcodes.CHECKCAST:
+        {
+          DexType type = getNextDexTypeOperand(view);
+          EV value = getNextValueOperand(view);
+          onCheckCast(type, value);
+          return;
+        }
       case LirOpcodes.DEBUGPOS:
         {
           onDebugPosition();
@@ -636,7 +917,7 @@
         }
       case LirOpcodes.PHI:
         {
-          DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
+          DexType type = getNextDexTypeOperand(view);
           List<EV> operands = new ArrayList<>();
           while (view.hasMoreOperands()) {
             operands.add(getNextValueOperand(view));
@@ -651,7 +932,7 @@
         }
       case LirOpcodes.MOVEEXCEPTION:
         {
-          DexType type = (DexType) getConstantItem(view.getNextConstantOperand());
+          DexType type = getNextDexTypeOperand(view);
           onMoveException(type);
           return;
         }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index 19d5350..132fc61 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
@@ -129,18 +130,47 @@
   }
 
   @Override
+  public void onConstFloat(int value) {
+    appendOutValue().append(Float.intBitsToFloat(value));
+  }
+
+  @Override
+  public void onConstLong(long value) {
+    appendOutValue().append(value);
+  }
+
+  @Override
+  public void onConstDouble(long value) {
+    appendOutValue().append(Double.longBitsToDouble(value));
+  }
+
+  @Override
   public void onConstString(DexString string) {
     appendOutValue().append("str(").append(string).append(")");
   }
 
   @Override
+  public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+    appendOutValue();
+    appendValueArguments(leftValueIndex, rightValueIndex);
+  }
+
+  @Override
+  public void onSub(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+    appendOutValue();
+    appendValueArguments(leftValueIndex, rightValueIndex);
+  }
+
+  @Override
   public void onDiv(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-    appendOutValue()
-        .append(fmtValueIndex(leftValueIndex))
-        .append(' ')
-        .append(fmtValueIndex(rightValueIndex))
-        .append(' ')
-        .append(type);
+    appendOutValue();
+    appendValueArguments(leftValueIndex, rightValueIndex);
+  }
+
+  @Override
+  public void onXor(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+    appendOutValue();
+    appendValueArguments(leftValueIndex, rightValueIndex);
   }
 
   @Override
@@ -151,7 +181,14 @@
 
   @Override
   public void onIf(IfType ifKind, int blockIndex, EV valueIndex) {
-    builder.append(fmtValueIndex(valueIndex)).append(' ').append(fmtInsnIndex(blockIndex));
+    appendValueArguments(valueIndex);
+    builder.append(fmtInsnIndex(blockIndex));
+  }
+
+  @Override
+  public void onIfCmp(IfType ifKind, int blockIndex, EV leftValueIndex, EV rightValueIndex) {
+    appendValueArguments(leftValueIndex, rightValueIndex);
+    builder.append(fmtInsnIndex(blockIndex));
   }
 
   @Override
@@ -175,6 +212,12 @@
   }
 
   @Override
+  public void onNewInstance(DexType clazz) {
+    appendOutValue();
+    builder.append(clazz);
+  }
+
+  @Override
   public void onInvokeMethodInstruction(DexMethod method, List<EV> arguments) {
     if (!method.getReturnType().isVoidType()) {
       appendOutValue();
@@ -225,11 +268,43 @@
   }
 
   @Override
+  public void onReturn(EV value) {
+    appendValueArguments(value);
+  }
+
+  @Override
   public void onArrayLength(EV arrayValueIndex) {
     appendOutValue().append(fmtValueIndex(arrayValueIndex));
   }
 
   @Override
+  public void onCheckCast(DexType type, EV value) {
+    appendOutValue();
+    appendValueArguments(value);
+    builder.append(type);
+  }
+
+  @Override
+  public void onArrayGetPrimitive(MemberType type, EV array, EV index) {
+    appendOutValue();
+    appendValueArguments(array, index);
+    builder.append(type);
+  }
+
+  @Override
+  public void onArrayGetObject(DexType type, EV array, EV index) {
+    appendOutValue();
+    appendValueArguments(array, index);
+    builder.append(type);
+  }
+
+  @Override
+  public void onArrayPut(MemberType type, EV array, EV index, EV value) {
+    appendValueArguments(array, index, value);
+    builder.append(type);
+  }
+
+  @Override
   public void onDebugPosition() {
     // Nothing to append.
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 7b17f6d..d5fd889 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -222,7 +222,7 @@
   private final Map<Signature, Signature> signatureMap = new ConcurrentHashMap<>();
   private final LinkedHashSet<MapVersionMappingInformation> mapVersions;
   private final Map<String, String> originalSourceFiles;
-  private final List<String> preamble;
+  private List<String> preamble;
 
   private ClassNameMapper(
       ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
@@ -243,6 +243,10 @@
     return preamble;
   }
 
+  public void setPreamble(List<String> preamble) {
+    this.preamble = preamble;
+  }
+
   private Signature canonicalizeSignature(Signature signature) {
     Signature result = signatureMap.get(signature);
     if (result != null) {
diff --git a/src/main/java/com/android/tools/r8/naming/MapConsumer.java b/src/main/java/com/android/tools/r8/naming/MapConsumer.java
new file mode 100644
index 0000000..236c2b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/MapConsumer.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Finishable;
+
+/**
+ * This is an internal consumer that can accept our internal representation of a mapping format.
+ * This should not be exposed.
+ */
+public interface MapConsumer extends Finishable {
+
+  void accept(
+      DiagnosticsHandler diagnosticsHandler,
+      ProguardMapMarkerInfo makerInfo,
+      ClassNameMapper classNameMapper);
+}
diff --git a/src/main/java/com/android/tools/r8/naming/MultiProguardMapConsumer.java b/src/main/java/com/android/tools/r8/naming/MultiProguardMapConsumer.java
deleted file mode 100644
index 5901fd3..0000000
--- a/src/main/java/com/android/tools/r8/naming/MultiProguardMapConsumer.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.naming;
-
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProguardMapConsumer;
-import java.util.ArrayList;
-import java.util.List;
-
-public class MultiProguardMapConsumer extends ProguardMapConsumer {
-
-  private final List<ProguardMapConsumer> proguardMapConsumers;
-
-  public MultiProguardMapConsumer(List<ProguardMapConsumer> proguardMapConsumers) {
-    this.proguardMapConsumers = proguardMapConsumers;
-  }
-
-  @Override
-  public void finished(DiagnosticsHandler handler) {
-    proguardMapConsumers.forEach(consumer -> consumer.finished(handler));
-  }
-
-  @Override
-  public void accept(ProguardMapMarkerInfo markerInfo, ClassNameMapper classNameMapper) {
-    proguardMapConsumers.forEach(consumer -> consumer.accept(markerInfo, classNameMapper));
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-
-    private final List<ProguardMapConsumer> proguardMapConsumers = new ArrayList<>();
-
-    public Builder addProguardMapConsumer(ProguardMapConsumer consumer) {
-      proguardMapConsumers.add(consumer);
-      return this;
-    }
-
-    public MultiProguardMapConsumer build() {
-      return new MultiProguardMapConsumer(proguardMapConsumers);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapPartitionConsumer.java b/src/main/java/com/android/tools/r8/naming/ProguardMapPartitionConsumer.java
deleted file mode 100644
index 451fd6c..0000000
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapPartitionConsumer.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.naming;
-
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProguardMapConsumer;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.retrace.MappingPartition;
-import com.android.tools.r8.retrace.MappingPartitionMetadata;
-import com.android.tools.r8.retrace.ProguardMapPartitioner;
-import com.android.tools.r8.retrace.internal.ProguardMapProducerInternal;
-import java.io.IOException;
-import java.util.function.Consumer;
-
-public class ProguardMapPartitionConsumer extends ProguardMapConsumer {
-
-  private final Consumer<MappingPartition> mappingPartitionConsumer;
-  private final Consumer<MappingPartitionMetadata> metadataConsumer;
-  private final Runnable finishedConsumer;
-  private final DiagnosticsHandler diagnosticsHandler;
-
-  private ProguardMapPartitionConsumer(
-      Consumer<MappingPartition> mappingPartitionConsumer,
-      Consumer<MappingPartitionMetadata> metadataConsumer,
-      Runnable finishedConsumer,
-      DiagnosticsHandler diagnosticsHandler) {
-    this.mappingPartitionConsumer = mappingPartitionConsumer;
-    this.metadataConsumer = metadataConsumer;
-    this.finishedConsumer = finishedConsumer;
-    this.diagnosticsHandler = diagnosticsHandler;
-  }
-
-  @Override
-  public void accept(ProguardMapMarkerInfo makerInfo, ClassNameMapper classNameMapper) {
-    try {
-      // TODO(b/274735214): Ensure we get markerInfo consumed as well.
-      MappingPartitionMetadata run =
-          ProguardMapPartitioner.builder(diagnosticsHandler)
-              .setProguardMapProducer(new ProguardMapProducerInternal(classNameMapper))
-              .setPartitionConsumer(mappingPartitionConsumer)
-              // Setting these do not actually do anything currently since there is no parsing.
-              .setAllowEmptyMappedRanges(false)
-              .setAllowExperimentalMapping(false)
-              .build()
-              .run();
-      metadataConsumer.accept(run);
-    } catch (IOException exception) {
-      throw new Unreachable("IOExceptions should only occur when parsing");
-    }
-  }
-
-  @Override
-  public void finished(DiagnosticsHandler handler) {
-    finishedConsumer.run();
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder {
-
-    private Consumer<MappingPartition> mappingPartitionConsumer;
-    private Consumer<MappingPartitionMetadata> metadataConsumer;
-    private Runnable finishedConsumer;
-    private DiagnosticsHandler diagnosticsHandler;
-
-    public Builder setMappingPartitionConsumer(
-        Consumer<MappingPartition> mappingPartitionConsumer) {
-      this.mappingPartitionConsumer = mappingPartitionConsumer;
-      return this;
-    }
-
-    public Builder setMetadataConsumer(Consumer<MappingPartitionMetadata> metadataConsumer) {
-      this.metadataConsumer = metadataConsumer;
-      return this;
-    }
-
-    public Builder setFinishedConsumer(Runnable finishedConsumer) {
-      this.finishedConsumer = finishedConsumer;
-      return this;
-    }
-
-    public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
-      this.diagnosticsHandler = diagnosticsHandler;
-      return this;
-    }
-
-    public ProguardMapPartitionConsumer build() {
-      return new ProguardMapPartitionConsumer(
-          mappingPartitionConsumer, metadataConsumer, finishedConsumer, diagnosticsHandler);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java b/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java
index 95bbd38..be3d3ba 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java
@@ -5,36 +5,36 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProguardMapConsumer;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 
 /***
- * Default implementation of a ProguardMapConsumer that wraps around a string consumer for streamed
- * string output.
+ * Default implementation of a MapConsumer that wraps around a string consumer for streamed string
+ * output.
  */
-public class ProguardMapStringConsumer extends ProguardMapConsumer
-    implements ChainableStringConsumer {
+public class ProguardMapStringConsumer implements MapConsumer, ChainableStringConsumer {
 
   private final StringConsumer stringConsumer;
-  private final DiagnosticsHandler diagnosticsHandler;
+  private DiagnosticsHandler diagnosticsHandler;
 
-  private ProguardMapStringConsumer(
-      StringConsumer stringConsumer, DiagnosticsHandler diagnosticsHandler) {
+  private ProguardMapStringConsumer(StringConsumer stringConsumer) {
     assert stringConsumer != null;
-    assert diagnosticsHandler != null;
     this.stringConsumer = stringConsumer;
-    this.diagnosticsHandler = diagnosticsHandler;
   }
 
   @Override
-  public void accept(ProguardMapMarkerInfo markerInfo, ClassNameMapper classNameMapper) {
+  public void accept(
+      DiagnosticsHandler diagnosticsHandler,
+      ProguardMapMarkerInfo markerInfo,
+      ClassNameMapper classNameMapper) {
+    this.diagnosticsHandler = diagnosticsHandler;
     accept(markerInfo.serializeToString());
     classNameMapper.write(this);
   }
 
   @Override
   public ChainableStringConsumer accept(String string) {
+    assert diagnosticsHandler != null;
     stringConsumer.accept(string, diagnosticsHandler);
     return this;
   }
@@ -55,20 +55,14 @@
   public static class Builder {
 
     private StringConsumer stringConsumer;
-    private DiagnosticsHandler diagnosticsHandler;
 
     public Builder setStringConsumer(StringConsumer stringConsumer) {
       this.stringConsumer = stringConsumer;
       return this;
     }
 
-    public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
-      this.diagnosticsHandler = diagnosticsHandler;
-      return this;
-    }
-
     public ProguardMapStringConsumer build() {
-      return new ProguardMapStringConsumer(stringConsumer, diagnosticsHandler);
+      return new ProguardMapStringConsumer(stringConsumer);
     }
   }
 }
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 1d92c11..0637406 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.MapIdEnvironment;
 import com.android.tools.r8.MapIdProvider;
-import com.android.tools.r8.ProguardMapConsumer;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -44,7 +43,7 @@
 
   private final ClassNameMapper classNameMapper;
   private final InternalOptions options;
-  private final ProguardMapConsumer consumer;
+  private final MapConsumer consumer;
   private final Reporter reporter;
   private final Tool compiler;
 
@@ -53,7 +52,7 @@
     this.classNameMapper = classNameMapper.sorted();
     // TODO(b/217111432): Validate Proguard using ProguardMapChecker without building the entire
     //  Proguard map in memory.
-    this.consumer = options.proguardMapConsumer;
+    this.consumer = options.mapConsumer;
     this.options = options;
     this.reporter = options.reporter;
     this.compiler = tool;
@@ -68,6 +67,7 @@
   public ProguardMapId writeProguardMap() {
     ProguardMapId proguardMapId = computeProguardMapId();
     consumer.accept(
+        reporter,
         ProguardMapMarkerInfo.builder()
             .setCompilerName(compiler.name())
             .setProguardMapId(proguardMapId)
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index eff4f92..1f5b5ff 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodCollection;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
index bc1dfda..6bb861a 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
@@ -22,18 +22,15 @@
   public static ArtProfileCollection createInitialArtProfileCollection(
       AppInfo appInfo, InternalOptions options) {
     ArtProfileOptions artProfileOptions = options.getArtProfileOptions();
-    Collection<ArtProfileForRewriting> artProfilesForRewriting =
-        artProfileOptions.getArtProfilesForRewriting();
+    Collection<ArtProfileProvider> artProfileProviders = artProfileOptions.getArtProfileProviders();
     List<ArtProfile> artProfiles =
         new ArrayList<>(
-            artProfilesForRewriting.size()
+            artProfileProviders.size()
                 + BooleanUtils.intValue(artProfileOptions.isCompletenessCheckForTestingEnabled()));
-    for (ArtProfileForRewriting artProfileForRewriting :
-        options.getArtProfileOptions().getArtProfilesForRewriting()) {
-      ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
+    for (ArtProfileProvider artProfileProvider : artProfileProviders) {
       ArtProfile.Builder artProfileBuilder =
           ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
-      artProfileForRewriting.getArtProfileProvider().getArtProfile(artProfileBuilder);
+      artProfileProvider.getArtProfile(artProfileBuilder);
       artProfiles.add(artProfileBuilder.build());
     }
     if (artProfileOptions.isCompletenessCheckForTestingEnabled()) {
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
index d5f91da..4d60860 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileOptions.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
 
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import java.util.Collection;
 import java.util.Collections;
 
@@ -18,6 +19,8 @@
   private Collection<ArtProfileForRewriting> artProfilesForRewriting = Collections.emptyList();
   private boolean enableCompletenessCheckForTesting =
       parseSystemPropertyOrDefault(COMPLETENESS_PROPERTY_KEY, false);
+  private boolean hasReadArtProfileProviders = false;
+  private boolean allowReadingEmptyArtProfileProvidersMultipleTimesForTesting = false;
 
   private final InternalOptions options;
 
@@ -29,6 +32,18 @@
     return artProfilesForRewriting;
   }
 
+  public Collection<ArtProfileProvider> getArtProfileProviders() {
+    assert !hasReadArtProfileProviders
+        || (allowReadingEmptyArtProfileProvidersMultipleTimesForTesting
+            && artProfilesForRewriting.isEmpty());
+    hasReadArtProfileProviders = true;
+    return ListUtils.map(artProfilesForRewriting, ArtProfileForRewriting::getArtProfileProvider);
+  }
+
+  public InternalOptions getOptions() {
+    return options;
+  }
+
   public boolean isCompletenessCheckForTestingEnabled() {
     return enableCompletenessCheckForTesting
         && !options.isDesugaredLibraryCompilation()
@@ -73,6 +88,13 @@
     return enableCompletenessCheckForTesting;
   }
 
+  public ArtProfileOptions setAllowReadingEmptyArtProfileProvidersMultipleTimesForTesting(
+      boolean allowReadingEmptyArtProfileProvidersMultipleTimesForTesting) {
+    this.allowReadingEmptyArtProfileProvidersMultipleTimesForTesting =
+        allowReadingEmptyArtProfileProvidersMultipleTimesForTesting;
+    return this;
+  }
+
   public ArtProfileOptions setArtProfilesForRewriting(Collection<ArtProfileForRewriting> inputs) {
     this.artProfilesForRewriting = inputs;
     return this;
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
index a9d901a..94232e2 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingCfInstructionDesugaringEventConsumer.java
@@ -91,8 +91,9 @@
   }
 
   @Override
-  public void acceptCompanionClassClinit(ProgramMethod method) {
-    parent.acceptCompanionClassClinit(method);
+  public void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(companionMethod, method);
+    parent.acceptCompanionClassClinit(method, companionMethod);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingRootSetBuilderEventConsumer.java b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingRootSetBuilderEventConsumer.java
index 2a10ce0..22691ca 100644
--- a/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingRootSetBuilderEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/profile/rewriting/ProfileRewritingRootSetBuilderEventConsumer.java
@@ -28,8 +28,9 @@
   }
 
   @Override
-  public void acceptCompanionClassClinit(ProgramMethod method) {
-    parent.acceptCompanionClassClinit(method);
+  public void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod) {
+    additionsCollection.addMethodAndHolderIfContextIsInProfile(companionMethod, method);
+    parent.acceptCompanionClassClinit(method, companionMethod);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index cffb1cf..ff4fc92 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.ProgramPackage;
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.SortedProgramPackageCollection;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
 import com.android.tools.r8.graph.lens.NestedGraphLens;
 import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
 import com.android.tools.r8.repackaging.RepackagingLens.Builder;
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 add5db3..598f71f 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplier.java
@@ -6,25 +6,11 @@
 
 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 java.util.Set;
+import com.android.tools.r8.MappingSupplierInternal;
 
 @Keep
-public abstract class MappingSupplier<T extends MappingSupplier<T>> {
+public interface MappingSupplier<T extends MappingSupplier<T>>
+    extends MappingSupplierBase<T>, MappingSupplierInternal {
 
-  /***
-   * Register an allowed mapping lookup to allow for prefetching of resources.
-   *
-   * @param classReference The minified class reference allowed to be lookup up.
-   */
-  public abstract T registerClassUse(
-      DiagnosticsHandler diagnosticsHandler, ClassReference classReference);
-
-  public abstract void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler);
-
-  public abstract Set<MapVersionMappingInformation> getMapVersions(
-      DiagnosticsHandler diagnosticsHandler);
-
-  public abstract Retracer createRetracer(DiagnosticsHandler diagnosticsHandler);
+  Retracer createRetracer(DiagnosticsHandler diagnosticsHandler);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplierAsync.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplierAsync.java
new file mode 100644
index 0000000..9ffa613
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplierAsync.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+
+public interface MappingSupplierAsync<T extends MappingSupplierAsync<T>>
+    extends MappingSupplierBase<T> {
+
+  Retracer createRetracer(
+      DiagnosticsHandler diagnosticsHandler, MappingPartitionFromKeySupplier supplier);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplierBase.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBase.java
new file mode 100644
index 0000000..accecec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBase.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+
+@Keep
+public interface MappingSupplierBase<T extends MappingSupplierBase<T>> {
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param classReference The minified class reference allowed to be lookup up.
+   */
+  T registerClassUse(DiagnosticsHandler diagnosticsHandler, ClassReference classReference);
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param methodReference The minified method reference allowed to be lookup up.
+   */
+  T registerMethodUse(DiagnosticsHandler diagnosticsHandler, MethodReference methodReference);
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param fieldReference The minified field reference allowed to be lookup up.
+   */
+  T registerFieldUse(DiagnosticsHandler diagnosticsHandler, FieldReference fieldReference);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
index d56aee5..ebcfaf9 100644
--- a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
@@ -4,63 +4,170 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.MapVersion;
-import com.android.tools.r8.retrace.internal.PartitionMappingSupplierBuilderImpl;
+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.retrace.internal.PartitionMappingSupplierBase;
 
 @Keep
-public abstract class PartitionMappingSupplier extends MappingSupplier<PartitionMappingSupplier> {
+public class PartitionMappingSupplier extends PartitionMappingSupplierBase<PartitionMappingSupplier>
+    implements MappingSupplier<PartitionMappingSupplier> {
+
+  private final MappingPartitionFromKeySupplier partitionSupplier;
+
+  private PartitionMappingSupplier(
+      RegisterMappingPartitionCallback registerCallback,
+      PrepareMappingPartitionsCallback prepareCallback,
+      MappingPartitionFromKeySupplier partitionSupplier,
+      boolean allowExperimental,
+      byte[] metadata,
+      MapVersion fallbackMapVersion) {
+    super(registerCallback, prepareCallback, allowExperimental, metadata, fallbackMapVersion);
+    this.partitionSupplier = partitionSupplier;
+  }
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param classReference The minified class reference allowed to be lookup up.
+   */
+  @Keep
+  @Override
+  public PartitionMappingSupplier registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
+    return super.registerClassUse(diagnosticsHandler, classReference);
+  }
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param methodReference The minified method reference allowed to be lookup up.
+   */
+  @Keep
+  @Override
+  public PartitionMappingSupplier registerMethodUse(
+      DiagnosticsHandler diagnosticsHandler, MethodReference methodReference) {
+    return super.registerMethodUse(diagnosticsHandler, methodReference);
+  }
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param fieldReference The minified field reference allowed to be lookup up.
+   */
+  @Keep
+  @Override
+  public PartitionMappingSupplier registerFieldUse(
+      DiagnosticsHandler diagnosticsHandler, FieldReference fieldReference) {
+    return super.registerFieldUse(diagnosticsHandler, fieldReference);
+  }
+
+  @Override
+  public Retracer createRetracer(DiagnosticsHandler diagnosticsHandler) {
+    return createRetracerFromPartitionSupplier(diagnosticsHandler, partitionSupplier);
+  }
+
+  @Override
+  public PartitionMappingSupplier self() {
+    return this;
+  }
 
   public static Builder builder() {
-    return new PartitionMappingSupplierBuilderImpl(MapVersion.MAP_VERSION_NONE);
+    return new Builder();
   }
 
-  public static NoMetadataBuilder<?> noMetadataBuilder(MapVersion mapVersion) {
-    return new PartitionMappingSupplierBuilderImpl(mapVersion);
+  public static NoMetadataBuilder noMetadataBuilder(MapVersion mapVersion) {
+    return new NoMetadataBuilder(mapVersion);
   }
 
   @Keep
-  public abstract static class NoMetadataBuilder<B extends NoMetadataBuilder<B>>
-      extends MappingSupplierBuilder<PartitionMappingSupplier, B> {
+  public abstract static class NoMetadataBuilderBase<B extends NoMetadataBuilderBase<B>>
+      extends PartitionMappingSupplierBuilderBase<B> {
+
+    protected MappingPartitionFromKeySupplier partitionSupplier;
+
+    private NoMetadataBuilderBase(MapVersion fallbackMapVersion) {
+      super(fallbackMapVersion);
+    }
 
     /***
-     * Callback to be notified of a partition that is later going to be needed. When all needed
-     * partitions are found the callback specified to {@code setPrepareMappingPartitionsCallback} is
-     * called.
+     * Sets the partition supplier.
      *
-     * @param registerPartitionCallback the consumer to get keys for partitions.
+     * @param partitionSupplier the supplier of partitions for requested keys.
      */
-    public abstract Builder setRegisterMappingPartitionCallback(
-        RegisterMappingPartitionCallback registerPartitionCallback);
-
-    /***
-     * A callback notifying that all partitions should be prepared. The prepare callback is
-     * guaranteed to be called prior to any calls to the partition supplier.
-     *
-     * @param prepare the callback to listen for when partitions should be prepared.
-     */
-    public abstract Builder setPrepareMappingPartitionsCallback(
-        PrepareMappingPartitionsCallback prepare);
-
-    /***
-     * Set the partition supplier that is needed for retracing. All partitions needed has been
-     * declared earlier and this should block until the bytes associated with the partition is
-     * ready.
-     *
-     * @param partitionSupplier the function to return a partition to retrace
-     */
-    public abstract Builder setMappingPartitionFromKeySupplier(
-        MappingPartitionFromKeySupplier partitionSupplier);
+    public B setMappingPartitionFromKeySupplier(MappingPartitionFromKeySupplier partitionSupplier) {
+      this.partitionSupplier = partitionSupplier;
+      return self();
+    }
   }
 
   @Keep
-  public abstract static class Builder extends NoMetadataBuilder<Builder> {
+  public static class NoMetadataBuilder extends NoMetadataBuilderBase<NoMetadataBuilder> {
+
+    private NoMetadataBuilder(MapVersion fallbackMapVersion) {
+      super(fallbackMapVersion);
+    }
+
+    @Override
+    protected NoMetadataBuilder self() {
+      return this;
+    }
+
+    public PartitionMappingSupplier build() {
+      if (partitionSupplier == null) {
+        throw new RuntimeException("Cannot build without providing a partition supplier.");
+      }
+      return new PartitionMappingSupplier(
+          registerCallback,
+          prepareCallback,
+          partitionSupplier,
+          allowExperimental,
+          null,
+          fallbackMapVersion);
+    }
+  }
+
+  @Keep
+  public static class Builder extends NoMetadataBuilderBase<Builder> {
+
+    private byte[] metadata;
+
+    private Builder() {
+      super(MapVersion.MAP_VERSION_NONE);
+    }
+
+    @Override
+    protected Builder self() {
+      return this;
+    }
 
     /***
      * Sets the serialized metadata that was obtained when partitioning.
      *
      * @param metadata the serialized metadata
      */
-    public abstract Builder setMetadata(byte[] metadata);
+    public Builder setMetadata(byte[] metadata) {
+      this.metadata = metadata;
+      return self();
+    }
+
+    public PartitionMappingSupplier build() {
+      if (partitionSupplier == null) {
+        throw new RuntimeException("Cannot build without providing a partition supplier");
+      }
+      if (metadata == null) {
+        throw new RuntimeException("Cannot build without providing metadata.");
+      }
+      return new PartitionMappingSupplier(
+          registerCallback,
+          prepareCallback,
+          partitionSupplier,
+          allowExperimental,
+          metadata,
+          fallbackMapVersion);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierAsync.java b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierAsync.java
new file mode 100644
index 0000000..41acee5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierAsync.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.naming.MapVersion;
+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.retrace.internal.PartitionMappingSupplierBase;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
+
+@Keep
+public class PartitionMappingSupplierAsync
+    extends PartitionMappingSupplierBase<PartitionMappingSupplierAsync>
+    implements MappingSupplierAsync<PartitionMappingSupplierAsync> {
+
+  private PartitionMappingSupplierAsync(
+      RegisterMappingPartitionCallback registerCallback,
+      PrepareMappingPartitionsCallback prepareCallback,
+      boolean allowExperimental,
+      byte[] metadata,
+      MapVersion fallbackMapVersion) {
+    super(registerCallback, prepareCallback, allowExperimental, metadata, fallbackMapVersion);
+  }
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param classReference The minified class reference allowed to be lookup up.
+   */
+  @Keep
+  @Override
+  public PartitionMappingSupplierAsync registerClassUse(
+      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
+    return super.registerClassUse(diagnosticsHandler, classReference);
+  }
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param methodReference The minified method reference allowed to be lookup up.
+   */
+  @Keep
+  @Override
+  public PartitionMappingSupplierAsync registerMethodUse(
+      DiagnosticsHandler diagnosticsHandler, MethodReference methodReference) {
+    return super.registerMethodUse(diagnosticsHandler, methodReference);
+  }
+
+  /***
+   * Register an allowed mapping lookup to allow for prefetching of resources.
+   *
+   * @param fieldReference The minified field reference allowed to be lookup up.
+   */
+  @Keep
+  @Override
+  public PartitionMappingSupplierAsync registerFieldUse(
+      DiagnosticsHandler diagnosticsHandler, FieldReference fieldReference) {
+    return super.registerFieldUse(diagnosticsHandler, fieldReference);
+  }
+
+  @Override
+  public PartitionMappingSupplierAsync self() {
+    return this;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public RetracerImpl createRetracer(
+      DiagnosticsHandler diagnosticsHandler, MappingPartitionFromKeySupplier supplier) {
+    return createRetracerFromPartitionSupplier(diagnosticsHandler, supplier);
+  }
+
+  @Keep
+  public static class Builder extends PartitionMappingSupplierBuilderBase<Builder> {
+
+    private byte[] metadata;
+
+    private Builder() {
+      super(MapVersion.MAP_VERSION_NONE);
+    }
+
+    /***
+     * Sets the serialized metadata that was obtained when partitioning.
+     *
+     * @param metadata the serialized metadata
+     */
+    public Builder setMetadata(byte[] metadata) {
+      this.metadata = metadata;
+      return self();
+    }
+
+    @Override
+    protected Builder self() {
+      return this;
+    }
+
+    public PartitionMappingSupplierAsync build() {
+      if (metadata == null) {
+        throw new RuntimeException("Cannot build without providing metadata.");
+      }
+      return new PartitionMappingSupplierAsync(
+          registerCallback, prepareCallback, allowExperimental, metadata, fallbackMapVersion);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierBuilderBase.java b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierBuilderBase.java
new file mode 100644
index 0000000..f5b020b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierBuilderBase.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.naming.MapVersion;
+
+@Keep
+public abstract class PartitionMappingSupplierBuilderBase<
+    T extends PartitionMappingSupplierBuilderBase<T>> {
+
+  protected RegisterMappingPartitionCallback registerCallback =
+      RegisterMappingPartitionCallback.empty();
+  protected PrepareMappingPartitionsCallback prepareCallback =
+      PrepareMappingPartitionsCallback.empty();
+  protected final MapVersion fallbackMapVersion;
+  protected boolean allowExperimental = false;
+
+  public PartitionMappingSupplierBuilderBase(MapVersion fallbackMapVersion) {
+    this.fallbackMapVersion = fallbackMapVersion;
+  }
+
+  public T setRegisterMappingPartitionCallback(RegisterMappingPartitionCallback registerCallback) {
+    this.registerCallback = registerCallback;
+    return self();
+  }
+
+  public T setPrepareMappingPartitionsCallback(PrepareMappingPartitionsCallback prepareCallback) {
+    this.prepareCallback = prepareCallback;
+    return self();
+  }
+
+  public T setAllowExperimental(boolean allowExperimental) {
+    this.allowExperimental = allowExperimental;
+    return self();
+  }
+
+  protected abstract T self();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/PrepareMappingPartitionsCallback.java b/src/main/java/com/android/tools/r8/retrace/PrepareMappingPartitionsCallback.java
index b19babd..89c6161 100644
--- a/src/main/java/com/android/tools/r8/retrace/PrepareMappingPartitionsCallback.java
+++ b/src/main/java/com/android/tools/r8/retrace/PrepareMappingPartitionsCallback.java
@@ -15,5 +15,11 @@
 @Keep
 public interface PrepareMappingPartitionsCallback {
 
+  PrepareMappingPartitionsCallback EMPTY_INSTANCE = () -> {};
+
+  static PrepareMappingPartitionsCallback empty() {
+    return EMPTY_INSTANCE;
+  }
+
   void prepare();
 }
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 28b95a0..7b3e657 100644
--- a/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMappingSupplier.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.retrace.internal.ProguardMappingSupplierBuilderImpl;
 
 @Keep
-public abstract class ProguardMappingSupplier extends MappingSupplier<ProguardMappingSupplier> {
+public abstract class ProguardMappingSupplier implements MappingSupplier<ProguardMappingSupplier> {
 
   public static Builder builder() {
     return new ProguardMappingSupplierBuilderImpl();
diff --git a/src/main/java/com/android/tools/r8/retrace/RegisterMappingPartitionCallback.java b/src/main/java/com/android/tools/r8/retrace/RegisterMappingPartitionCallback.java
index df5c943..e411883 100644
--- a/src/main/java/com/android/tools/r8/retrace/RegisterMappingPartitionCallback.java
+++ b/src/main/java/com/android/tools/r8/retrace/RegisterMappingPartitionCallback.java
@@ -13,5 +13,11 @@
 @Keep
 public interface RegisterMappingPartitionCallback {
 
+  RegisterMappingPartitionCallback EMPTY_INSTANCE = key -> {};
+
+  static RegisterMappingPartitionCallback empty() {
+    return EMPTY_INSTANCE;
+  }
+
   void register(String key);
 }
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 a8cabe6..5cca6e8 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -10,22 +10,17 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.Version;
-import com.android.tools.r8.retrace.internal.ResultWithContextImpl;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
+import com.android.tools.r8.retrace.internal.RetraceBase;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
 import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Charsets;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
@@ -35,16 +30,8 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Scanner;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
 /**
  * A retrace tool for obfuscated stack traces.
@@ -53,7 +40,7 @@
  * tool.
  */
 @Keep
-public class Retrace<T, ST extends StackTraceElementProxy<T, ST>> {
+public class Retrace<T, ST extends StackTraceElementProxy<T, ST>> extends RetraceBase<T, ST> {
 
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
@@ -129,7 +116,7 @@
     return builder;
   }
 
-  private static MappingSupplier<?> getMappingSupplier(
+  private static ProguardMappingSupplier getMappingSupplier(
       String mappingPath, DiagnosticsHandler diagnosticsHandler) {
     Path path = Paths.get(mappingPath);
     if (!Files.exists(path)) {
@@ -156,20 +143,17 @@
     }
   }
 
-  private final StackTraceLineParser<T, ST> stackTraceLineParser;
   private final MappingSupplier<?> mappingSupplier;
   private final DiagnosticsHandler diagnosticsHandler;
-  protected final boolean isVerbose;
 
   Retrace(
       StackTraceLineParser<T, ST> stackTraceLineParser,
       MappingSupplier<?> mappingSupplier,
       DiagnosticsHandler diagnosticsHandler,
       boolean isVerbose) {
-    this.stackTraceLineParser = stackTraceLineParser;
+    super(stackTraceLineParser, mappingSupplier, diagnosticsHandler, isVerbose);
     this.mappingSupplier = mappingSupplier;
     this.diagnosticsHandler = diagnosticsHandler;
-    this.isVerbose = isVerbose;
   }
 
   /**
@@ -179,19 +163,9 @@
    * @param context The context to retrace the stack trace in
    * @return list of potentially ambiguous stack traces.
    */
-  public ResultWithContext<List<List<T>>> retraceStackTrace(
+  public RetraceStackTraceResult<T> retraceStackTrace(
       List<T> stackTrace, RetraceStackTraceContext context) {
-    ListUtils.forEachWithIndex(
-        stackTrace,
-        (line, lineNumber) -> {
-          if (line == null) {
-            diagnosticsHandler.error(
-                RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
-            throw new RetraceAbortException();
-          }
-        });
-    List<ST> parsed = ListUtils.map(stackTrace, stackTraceLineParser::parse);
-    return retraceStackTraceParsed(parsed, context);
+    return retraceStackTraceParsed(parse(stackTrace), context);
   }
 
   /**
@@ -201,53 +175,11 @@
    * @param context The context to retrace the stack trace in
    * @return list of potentially ambiguous stack traces.
    */
-  public ResultWithContext<List<List<T>>> retraceStackTraceParsed(
+  public RetraceStackTraceResult<T> retraceStackTraceParsed(
       List<ST> stackTrace, RetraceStackTraceContext context) {
-    RetraceStackTraceElementProxyEquivalence<T, ST> equivalence =
-        new RetraceStackTraceElementProxyEquivalence<>(isVerbose);
-    stackTrace.forEach(proxy -> proxy.registerUses(mappingSupplier, diagnosticsHandler));
-    StackTraceElementProxyRetracer<T, ST> proxyRetracer =
-        StackTraceElementProxyRetracer.createDefault(
-            mappingSupplier.createRetracer(diagnosticsHandler));
-    List<List<List<T>>> finalResult = new ArrayList<>();
-    RetraceStackTraceContext finalContext =
-        ListUtils.fold(
-            stackTrace,
-            context,
-            (newContext, stackTraceLine) -> {
-              List<Pair<RetraceStackTraceElementProxy<T, ST>, List<T>>> resultsForLine =
-                  new ArrayList<>();
-              Box<List<T>> currentList = new Box<>();
-              Set<Wrapper<RetraceStackTraceElementProxy<T, ST>>> seen = new HashSet<>();
-              List<RetraceStackTraceContext> contexts = new ArrayList<>();
-              RetraceStackTraceElementProxyResult<T, ST> retraceResult =
-                  proxyRetracer.retrace(stackTraceLine, newContext);
-              retraceResult.stream()
-                  .forEach(
-                      retracedElement -> {
-                        if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
-                          if (seen.add(equivalence.wrap(retracedElement))) {
-                            currentList.set(new ArrayList<>());
-                            resultsForLine.add(Pair.create(retracedElement, currentList.get()));
-                            contexts.add(retracedElement.getContext());
-                          } else {
-                            currentList.clear();
-                          }
-                        }
-                        if (currentList.isSet()) {
-                          currentList
-                              .get()
-                              .add(stackTraceLine.toRetracedItem(retracedElement, isVerbose));
-                        }
-                      });
-              resultsForLine.sort(Comparator.comparing(Pair::getFirst));
-              finalResult.add(ListUtils.map(resultsForLine, Pair::getSecond));
-              if (contexts.isEmpty()) {
-                return retraceResult.getResultContext();
-              }
-              return contexts.size() == 1 ? contexts.get(0) : RetraceStackTraceContext.empty();
-            });
-    return ResultWithContextImpl.create(finalResult, finalContext);
+    registerUses(stackTrace);
+    return retraceStackTraceParsedWithRetracer(
+        mappingSupplier.createRetracer(diagnosticsHandler), stackTrace, context);
   }
 
   /**
@@ -255,34 +187,14 @@
    *
    * @param stackTraceFrame The frame to retrace that can give rise to ambiguous results
    * @param context The context to retrace the stack trace in
-   * @return A collection of retraced frame where each entry in the outer list is ambiguous
+   * @return A collection of potentially ambiguous retraced frames
    */
-  public ResultWithContext<List<T>> retraceFrame(
+  public RetraceStackFrameAmbiguousResultWithContext<T> retraceFrame(
       T stackTraceFrame, RetraceStackTraceContext context) {
-    Map<RetraceStackTraceElementProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
-    List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
-    ST parsedLine = stackTraceLineParser.parse(stackTraceFrame);
-    parsedLine.registerUses(mappingSupplier, diagnosticsHandler);
-    StackTraceElementProxyRetracer<T, ST> proxyRetracer =
-        StackTraceElementProxyRetracer.createDefault(
-            mappingSupplier.createRetracer(diagnosticsHandler));
-    Box<RetraceStackTraceContext> contextBox = new Box<>(context);
-    proxyRetracer.retrace(parsedLine, context).stream()
-        .forEach(
-            retracedElement -> {
-              if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
-                ambiguousKeys.add(retracedElement);
-                ambiguousBlocks.put(retracedElement, new ArrayList<>());
-              }
-              ambiguousBlocks
-                  .get(ListUtils.last(ambiguousKeys))
-                  .add(parsedLine.toRetracedItem(retracedElement, isVerbose));
-              contextBox.set(retracedElement.getContext());
-            });
-    Collections.sort(ambiguousKeys);
-    List<List<T>> retracedList = new ArrayList<>();
-    ambiguousKeys.forEach(key -> retracedList.add(ambiguousBlocks.get(key)));
-    return ResultWithContextImpl.create(retracedList, contextBox.get());
+    ST parsedFrame = parse(stackTraceFrame);
+    registerUses(parsedFrame);
+    return retraceFrameWithRetracer(
+        mappingSupplier.createRetracer(diagnosticsHandler), parsedFrame, context);
   }
 
   /**
@@ -293,22 +205,12 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced stack trace line
    */
-  public ResultWithContext<T> retraceLine(T stackTraceLine, RetraceStackTraceContext context) {
-    ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
-    parsedLine.registerUses(mappingSupplier, diagnosticsHandler);
-    StackTraceElementProxyRetracer<T, ST> proxyRetracer =
-        StackTraceElementProxyRetracer.createDefault(
-            mappingSupplier.createRetracer(diagnosticsHandler));
-    Box<RetraceStackTraceContext> contextBox = new Box<>(context);
-    List<T> result =
-        proxyRetracer.retrace(parsedLine, context).stream()
-            .map(
-                retraceFrame -> {
-                  contextBox.set(retraceFrame.getContext());
-                  return parsedLine.toRetracedItem(retraceFrame, isVerbose);
-                })
-            .collect(Collectors.toList());
-    return ResultWithContextImpl.create(result, contextBox.get());
+  public RetraceStackFrameResultWithContext<T> retraceLine(
+      T stackTraceLine, RetraceStackTraceContext context) {
+    ST parsedFrame = parse(stackTraceLine);
+    registerUses(parsedFrame);
+    return retraceLineWithRetracer(
+        mappingSupplier.createRetracer(diagnosticsHandler), parsedFrame, context);
   }
 
   /**
@@ -351,12 +253,13 @@
                 stackTraceLineParser, mappingSupplier, diagnosticsHandler, options.isVerbose());
         timing.end();
         timing.begin("Retracing");
-        ResultWithContext<String> result = stringRetracer.retraceParsed(parsedStackTrace, context);
+        RetraceStackFrameResultWithContext<String> result =
+            stringRetracer.retraceParsed(parsedStackTrace, context);
         timing.end();
         timing.begin("Report result");
         context = result.getContext();
         if (!result.isEmpty() || currentStackTrace.isEmpty()) {
-          command.getRetracedStackTraceConsumer().accept(result.getLines());
+          command.getRetracedStackTraceConsumer().accept(result.getResult());
         }
         timing.end();
       }
@@ -477,16 +380,13 @@
   }
 
   @Keep
-  public static class Builder<T, ST extends StackTraceElementProxy<T, ST>> {
+  public static class Builder<T, ST extends StackTraceElementProxy<T, ST>>
+      extends RetraceBuilderBase<Builder<T, ST>, T, ST> {
 
-    private StackTraceLineParser<T, ST> stackTraceLineParser;
     private MappingSupplier<?> mappingSupplier;
-    private DiagnosticsHandler diagnosticsHandler;
-    protected boolean isVerbose;
 
-    public Builder<T, ST> setStackTraceLineParser(
-        StackTraceLineParser<T, ST> stackTraceLineParser) {
-      this.stackTraceLineParser = stackTraceLineParser;
+    @Override
+    public Builder<T, ST> self() {
       return this;
     }
 
@@ -495,16 +395,6 @@
       return this;
     }
 
-    public Builder<T, ST> setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
-      this.diagnosticsHandler = diagnosticsHandler;
-      return this;
-    }
-
-    public Builder<T, ST> setVerbose(boolean isVerbose) {
-      this.isVerbose = isVerbose;
-      return this;
-    }
-
     public Retrace<T, ST> build() {
       return new Retrace<>(stackTraceLineParser, mappingSupplier, diagnosticsHandler, isVerbose);
     }
@@ -538,144 +428,4 @@
       }
     }
   }
-
-  private static class RetraceStackTraceElementProxyEquivalence<
-          T, ST extends StackTraceElementProxy<T, ST>>
-      extends Equivalence<RetraceStackTraceElementProxy<T, ST>> {
-
-    private final boolean isVerbose;
-
-    public RetraceStackTraceElementProxyEquivalence(boolean isVerbose) {
-      this.isVerbose = isVerbose;
-    }
-
-    @Override
-    protected boolean doEquivalent(
-        RetraceStackTraceElementProxy<T, ST> one, RetraceStackTraceElementProxy<T, ST> other) {
-      if (one == other) {
-        return true;
-      }
-      if (testNotEqualProperty(
-              one,
-              other,
-              RetraceStackTraceElementProxy::hasRetracedClass,
-              r -> r.getRetracedClass().getTypeName())
-          || testNotEqualProperty(
-              one,
-              other,
-              RetraceStackTraceElementProxy::hasSourceFile,
-              RetraceStackTraceElementProxy::getSourceFile)) {
-        return false;
-      }
-      assert one.getOriginalItem() == other.getOriginalItem();
-      if (isVerbose
-          || (one.getOriginalItem().hasLineNumber() && one.getOriginalItem().getLineNumber() > 0)) {
-        if (testNotEqualProperty(
-            one,
-            other,
-            RetraceStackTraceElementProxy::hasLineNumber,
-            RetraceStackTraceElementProxy::getLineNumber)) {
-          return false;
-        }
-      }
-      if (one.hasRetracedMethod() != other.hasRetracedMethod()) {
-        return false;
-      }
-      if (one.hasRetracedMethod()) {
-        RetracedMethodReference oneMethod = one.getRetracedMethod();
-        RetracedMethodReference otherMethod = other.getRetracedMethod();
-        if (oneMethod.isKnown() != otherMethod.isKnown()) {
-          return false;
-        }
-        // In verbose mode we check the signature, otherwise we only check the name
-        if (!oneMethod.getMethodName().equals(otherMethod.getMethodName())) {
-          return false;
-        }
-        if (isVerbose
-            && ((oneMethod.isKnown()
-                    && !oneMethod
-                        .asKnown()
-                        .getMethodReference()
-                        .toString()
-                        .equals(otherMethod.asKnown().getMethodReference().toString()))
-                || (!oneMethod.isKnown()
-                    && !oneMethod.getMethodName().equals(otherMethod.getMethodName())))) {
-          return false;
-        }
-      }
-      if (one.hasRetracedField() != other.hasRetracedField()) {
-        return false;
-      }
-      if (one.hasRetracedField()) {
-        RetracedFieldReference oneField = one.getRetracedField();
-        RetracedFieldReference otherField = other.getRetracedField();
-        if (oneField.isKnown() != otherField.isKnown()) {
-          return false;
-        }
-        if (!oneField.getFieldName().equals(otherField.getFieldName())) {
-          return false;
-        }
-        if (isVerbose
-            && ((oneField.isKnown()
-                    && !oneField
-                        .asKnown()
-                        .getFieldReference()
-                        .toString()
-                        .equals(otherField.asKnown().getFieldReference().toString()))
-                || (oneField.isUnknown()
-                    && !oneField.getFieldName().equals(otherField.getFieldName())))) {
-          return false;
-        }
-      }
-      if (one.hasRetracedFieldOrReturnType() != other.hasRetracedFieldOrReturnType()) {
-        return false;
-      }
-      if (one.hasRetracedFieldOrReturnType()) {
-        RetracedTypeReference oneFieldOrReturn = one.getRetracedFieldOrReturnType();
-        RetracedTypeReference otherFieldOrReturn = other.getRetracedFieldOrReturnType();
-        if (!compareRetracedTypeReference(oneFieldOrReturn, otherFieldOrReturn)) {
-          return false;
-        }
-      }
-      if (one.hasRetracedMethodArguments() != other.hasRetracedMethodArguments()) {
-        return false;
-      }
-      if (one.hasRetracedMethodArguments()) {
-        List<RetracedTypeReference> oneMethodArguments = one.getRetracedMethodArguments();
-        List<RetracedTypeReference> otherMethodArguments = other.getRetracedMethodArguments();
-        if (oneMethodArguments.size() != otherMethodArguments.size()) {
-          return false;
-        }
-        for (int i = 0; i < oneMethodArguments.size(); i++) {
-          if (compareRetracedTypeReference(
-              oneMethodArguments.get(i), otherMethodArguments.get(i))) {
-            return false;
-          }
-        }
-      }
-      return true;
-    }
-
-    private boolean compareRetracedTypeReference(
-        RetracedTypeReference one, RetracedTypeReference other) {
-      return one.isVoid() == other.isVoid()
-          && (one.isVoid() || one.getTypeName().equals(other.getTypeName()));
-    }
-
-    @Override
-    protected int doHash(RetraceStackTraceElementProxy<T, ST> proxy) {
-      return 0;
-    }
-
-    private <V extends Comparable<V>> boolean testNotEqualProperty(
-        RetraceStackTraceElementProxy<T, ST> one,
-        RetraceStackTraceElementProxy<T, ST> other,
-        Function<RetraceStackTraceElementProxy<T, ST>, Boolean> predicate,
-        Function<RetraceStackTraceElementProxy<T, ST>, V> getter) {
-      return Comparator.comparing(predicate)
-              .thenComparing(getter, Comparator.nullsFirst(V::compareTo))
-              .compare(one, other)
-          != 0;
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceAsync.java b/src/main/java/com/android/tools/r8/retrace/RetraceAsync.java
new file mode 100644
index 0000000..00e07bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceAsync.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.RetraceBase;
+import java.util.List;
+
+/** An async retrace tool for obfuscated stack traces. */
+@Keep
+public class RetraceAsync<T, ST extends StackTraceElementProxy<T, ST>> extends RetraceBase<T, ST> {
+
+  private final MappingSupplierAsync<?> mappingSupplier;
+  private final DiagnosticsHandler diagnosticsHandler;
+
+  RetraceAsync(
+      StackTraceLineParser<T, ST> stackTraceLineParser,
+      MappingSupplierAsync<?> mappingSupplier,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean isVerbose) {
+    super(stackTraceLineParser, mappingSupplier, diagnosticsHandler, isVerbose);
+    this.mappingSupplier = mappingSupplier;
+    this.diagnosticsHandler = diagnosticsHandler;
+  }
+
+  public static <T, ST extends StackTraceElementProxy<T, ST>>
+      RetraceAsync.Builder<T, ST> builder() {
+    return new Builder<>();
+  }
+
+  /**
+   * Retraces a complete stack frame and returns a list of retraced stack traces.
+   *
+   * @param stackTrace the stack trace to be retrace
+   * @param context The context to retrace the stack trace in
+   * @return list of potentially ambiguous stack traces.
+   */
+  public RetraceAsyncResult<RetraceStackTraceResult<T>> retraceStackTrace(
+      List<T> stackTrace, RetraceStackTraceContext context) {
+    return retraceStackTraceParsed(parse(stackTrace), context);
+  }
+
+  /**
+   * Retraces a complete stack frame and returns a list of retraced stack traces.
+   *
+   * @param stackTrace the stack trace to be retrace
+   * @param context The context to retrace the stack trace in
+   * @return list of potentially ambiguous stack traces.
+   */
+  public RetraceAsyncResult<RetraceStackTraceResult<T>> retraceStackTraceParsed(
+      List<ST> stackTrace, RetraceStackTraceContext context) {
+    registerUses(stackTrace);
+    return partitionSupplier ->
+        retraceStackTraceParsedWithRetracer(
+            mappingSupplier.createRetracer(diagnosticsHandler, partitionSupplier),
+            stackTrace,
+            context);
+  }
+
+  /**
+   * Retraces a stack trace frame with support for splitting up ambiguous results.
+   *
+   * @param stackTraceFrame The frame to retrace that can give rise to ambiguous results
+   * @param context The context to retrace the stack trace in
+   * @return A collection of retraced frame where each entry in the outer list is ambiguous
+   */
+  public RetraceAsyncResult<RetraceStackFrameAmbiguousResultWithContext<T>> retraceFrame(
+      T stackTraceFrame, RetraceStackTraceContext context) {
+    ST parsedFrame = parse(stackTraceFrame);
+    registerUses(parsedFrame);
+    return partitionSupplier ->
+        retraceFrameWithRetracer(
+            mappingSupplier.createRetracer(diagnosticsHandler, partitionSupplier),
+            parsedFrame,
+            context);
+  }
+
+  /**
+   * Utility method for tracing a single line that also retraces ambiguous lines without being able
+   * to distinguish them. For retracing with ambiguous results separated, use {@link #retraceFrame}
+   *
+   * @param stackTraceLine the stack trace line to retrace
+   * @param context The context to retrace the stack trace in
+   * @return the retraced stack trace line
+   */
+  public RetraceAsyncResult<RetraceStackFrameResultWithContext<T>> retraceLine(
+      T stackTraceLine, RetraceStackTraceContext context) {
+    ST parsedFrame = parse(stackTraceLine);
+    registerUses(parsedFrame);
+    return partitionSupplier ->
+        retraceLineWithRetracer(
+            mappingSupplier.createRetracer(diagnosticsHandler, partitionSupplier),
+            parsedFrame,
+            context);
+  }
+
+  @Keep
+  public static class Builder<T, ST extends StackTraceElementProxy<T, ST>>
+      extends RetraceBuilderBase<Builder<T, ST>, T, ST> {
+
+    private MappingSupplierAsync<?> mappingSupplier;
+
+    @Override
+    public RetraceAsync.Builder<T, ST> self() {
+      return this;
+    }
+
+    public RetraceAsync.Builder<T, ST> setMappingSupplier(MappingSupplierAsync<?> mappingSupplier) {
+      this.mappingSupplier = mappingSupplier;
+      return self();
+    }
+
+    public RetraceAsync<T, ST> build() {
+      return new RetraceAsync<>(
+          stackTraceLineParser, mappingSupplier, diagnosticsHandler, isVerbose);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceAsyncResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceAsyncResult.java
new file mode 100644
index 0000000..146c3df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceAsyncResult.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceAsyncResult<T> {
+
+  T getResult(MappingPartitionFromKeySupplier supplier);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBuilderBase.java b/src/main/java/com/android/tools/r8/retrace/RetraceBuilderBase.java
new file mode 100644
index 0000000..1deadfe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBuilderBase.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+
+@Keep
+public abstract class RetraceBuilderBase<
+    B extends RetraceBuilderBase<B, T, ST>, T, ST extends StackTraceElementProxy<T, ST>> {
+
+  protected StackTraceLineParser<T, ST> stackTraceLineParser;
+  protected DiagnosticsHandler diagnosticsHandler;
+  protected boolean isVerbose;
+
+  public abstract B self();
+
+  public B setStackTraceLineParser(StackTraceLineParser<T, ST> stackTraceLineParser) {
+    this.stackTraceLineParser = stackTraceLineParser;
+    return self();
+  }
+
+  public B setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
+    this.diagnosticsHandler = diagnosticsHandler;
+    return self();
+  }
+
+  public B setVerbose(boolean isVerbose) {
+    this.isVerbose = isVerbose;
+    return self();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceResultWithContext.java b/src/main/java/com/android/tools/r8/retrace/RetraceResultWithContext.java
new file mode 100644
index 0000000..f9dfcdb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceResultWithContext.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceResultWithContext {
+
+  RetraceStackTraceContext getContext();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResult.java
new file mode 100644
index 0000000..50e59c1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResult.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+@Keep
+public interface RetraceStackFrameAmbiguousResult<T> {
+
+  boolean isAmbiguous();
+
+  List<RetraceStackFrameResult<T>> getAmbiguousResult();
+
+  void forEach(Consumer<RetraceStackFrameResult<T>> consumer);
+
+  void forEachWithIndex(BiConsumer<RetraceStackFrameResult<T>, Integer> consumer);
+
+  int size();
+
+  boolean isEmpty();
+
+  RetraceStackFrameResult<T> get(int i);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResultWithContext.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResultWithContext.java
new file mode 100644
index 0000000..f856cbd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameAmbiguousResultWithContext.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceStackFrameAmbiguousResultWithContext<T>
+    extends RetraceStackFrameAmbiguousResult<T>, RetraceResultWithContext {}
diff --git a/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResult.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
rename to src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResult.java
index 4949aec..dcdbf0f 100644
--- a/src/main/java/com/android/tools/r8/retrace/ResultWithContext.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResult.java
@@ -9,13 +9,15 @@
 import java.util.function.Consumer;
 
 @Keep
-public interface ResultWithContext<T> {
+public interface RetraceStackFrameResult<T> {
 
-  RetraceStackTraceContext getContext();
-
-  List<T> getLines();
+  List<T> getResult();
 
   void forEach(Consumer<T> consumer);
 
+  int size();
+
+  T get(int i);
+
   boolean isEmpty();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResultWithContext.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResultWithContext.java
new file mode 100644
index 0000000..4a63db0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackFrameResultWithContext.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceStackFrameResultWithContext<T>
+    extends RetraceStackFrameResult<T>, RetraceResultWithContext {}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceResult.java
new file mode 100644
index 0000000..ab1e9eb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceResult.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import java.util.List;
+import java.util.function.Consumer;
+
+@Keep
+public interface RetraceStackTraceResult<T> extends RetraceResultWithContext {
+
+  List<RetraceStackFrameAmbiguousResult<T>> getResult();
+
+  void forEach(Consumer<RetraceStackFrameAmbiguousResult<T>> consumer);
+
+  boolean isEmpty();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
index d18f748..08598e7 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
@@ -46,7 +46,7 @@
       RetraceStackTraceElementProxy<T, ST> retracedProxy, boolean verbose);
 
   public void registerUses(
-      MappingSupplier<?> mappingSupplier, DiagnosticsHandler diagnosticsHandler) {
+      MappingSupplierBase<?> mappingSupplier, DiagnosticsHandler diagnosticsHandler) {
     if (hasClassName()) {
       mappingSupplier.registerClassUse(diagnosticsHandler, getClassReference());
     }
@@ -62,7 +62,9 @@
   }
 
   private static void registerUseFromTypeReference(
-      MappingSupplier<?> mappingSupplier, String typeName, DiagnosticsHandler diagnosticsHandler) {
+      MappingSupplierBase<?> mappingSupplier,
+      String typeName,
+      DiagnosticsHandler diagnosticsHandler) {
     TypeReference typeReference = Reference.typeFromTypeName(typeName);
     if (typeReference.isArray()) {
       typeReference = typeReference.asArray().getBaseType();
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 2810d8b..2c16ea1 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -8,9 +8,8 @@
 
 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.RetraceStackFrameResultWithContextImpl;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -77,36 +76,26 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced stack trace
    */
-  public ResultWithContext<String> retrace(
+  public RetraceStackFrameResultWithContext<String> retrace(
       List<String> stackTrace, RetraceStackTraceContext context) {
-    ResultWithContext<List<List<String>>> listResultWithContext =
+    RetraceStackTraceResult<String> listRetraceStackTraceResult =
         retraceStackTrace(stackTrace, context);
     List<String> retracedStrings = new ArrayList<>();
-    for (List<List<String>> newLines : listResultWithContext.getLines()) {
-      ListUtils.forEachWithIndex(
-          newLines,
-          (inlineFrames, ambiguousIndex) -> {
-            for (int i = 0; i < inlineFrames.size(); i++) {
-              String stackTraceLine = inlineFrames.get(i);
-              if (i == 0 && ambiguousIndex > 0) {
-                // We are reporting an ambiguous frame. To support retracing tools that retrace line
-                // by line we have to emit <OR> at the point of the first 'at ' if we can find it.
-                int indexToInsertOr = stackTraceLine.indexOf("at ");
-                if (indexToInsertOr < 0) {
-                  indexToInsertOr =
-                      Math.max(StringUtils.firstNonWhitespaceCharacter(stackTraceLine), 0);
-                }
-                retracedStrings.add(
-                    stackTraceLine.substring(0, indexToInsertOr)
-                        + "<OR> "
-                        + stackTraceLine.substring(indexToInsertOr));
-              } else {
-                retracedStrings.add(stackTraceLine);
-              }
-            }
-          });
-    }
-    return ResultWithContextImpl.create(retracedStrings, listResultWithContext.getContext());
+    listRetraceStackTraceResult.forEach(
+        newLines ->
+            newLines.forEachWithIndex(
+                (inlineFrames, ambiguousIndex) -> {
+                  for (int i = 0; i < inlineFrames.size(); i++) {
+                    String stackTraceLine = inlineFrames.get(i);
+                    if (i == 0 && ambiguousIndex > 0) {
+                      insertOrIntoStackTraceLine(stackTraceLine, retracedStrings);
+                    } else {
+                      retracedStrings.add(stackTraceLine);
+                    }
+                  }
+                }));
+    return RetraceStackFrameResultWithContextImpl.create(
+        retracedStrings, listRetraceStackTraceResult.getContext());
   }
 
   /**
@@ -117,36 +106,40 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced stack trace
    */
-  public ResultWithContext<String> retraceParsed(
+  public RetraceStackFrameResultWithContext<String> retraceParsed(
       List<StackTraceElementStringProxy> stackTrace, RetraceStackTraceContext context) {
-    ResultWithContext<List<List<String>>> listResultWithContext =
+    RetraceStackTraceResult<String> listRetraceStackTraceResult =
         retraceStackTraceParsed(stackTrace, context);
     List<String> retracedStrings = new ArrayList<>();
-    for (List<List<String>> newLines : listResultWithContext.getLines()) {
-      ListUtils.forEachWithIndex(
-          newLines,
-          (inlineFrames, ambiguousIndex) -> {
-            for (int i = 0; i < inlineFrames.size(); i++) {
-              String stackTraceLine = inlineFrames.get(i);
-              if (i == 0 && ambiguousIndex > 0) {
-                // We are reporting an ambiguous frame. To support retracing tools that retrace line
-                // by line we have to emit <OR> at the point of the first 'at ' if we can find it.
-                int indexToInsertOr = stackTraceLine.indexOf("at ");
-                if (indexToInsertOr < 0) {
-                  indexToInsertOr =
-                      Math.max(StringUtils.firstNonWhitespaceCharacter(stackTraceLine), 0);
-                }
-                retracedStrings.add(
-                    stackTraceLine.substring(0, indexToInsertOr)
-                        + "<OR> "
-                        + stackTraceLine.substring(indexToInsertOr));
-              } else {
-                retracedStrings.add(stackTraceLine);
-              }
-            }
-          });
+    listRetraceStackTraceResult.forEach(
+        newLines ->
+            newLines.forEachWithIndex(
+                (inlineFrames, ambiguousIndex) -> {
+                  for (int i = 0; i < inlineFrames.size(); i++) {
+                    String stackTraceLine = inlineFrames.get(i);
+                    if (i == 0 && ambiguousIndex > 0) {
+                      insertOrIntoStackTraceLine(stackTraceLine, retracedStrings);
+                    } else {
+                      retracedStrings.add(stackTraceLine);
+                    }
+                  }
+                }));
+    return RetraceStackFrameResultWithContextImpl.create(
+        retracedStrings, listRetraceStackTraceResult.getContext());
+  }
+
+  private void insertOrIntoStackTraceLine(String stackTraceLine, List<String> retracedStrings) {
+    // We are reporting an ambiguous frame. To support retracing tools that
+    // retrace line by line we have to emit <OR> at the point of the first 'at '
+    // if we can find it.
+    int indexToInsertOr = stackTraceLine.indexOf("at ");
+    if (indexToInsertOr < 0) {
+      indexToInsertOr = Math.max(StringUtils.firstNonWhitespaceCharacter(stackTraceLine), 0);
     }
-    return ResultWithContextImpl.create(retracedStrings, listResultWithContext.getContext());
+    retracedStrings.add(
+        stackTraceLine.substring(0, indexToInsertOr)
+            + "<OR> "
+            + stackTraceLine.substring(indexToInsertOr));
   }
 
   /**
@@ -156,12 +149,14 @@
    * @param context The context to retrace the stack trace in
    * @return the retraced frames
    */
-  public ResultWithContext<String> retrace(
+  public RetraceStackFrameResultWithContext<String> retrace(
       String stackTraceLine, RetraceStackTraceContext context) {
-    ResultWithContext<List<String>> listResultWithContext = retraceFrame(stackTraceLine, context);
+    RetraceStackFrameAmbiguousResultWithContext<String> listRetraceStackTraceResult =
+        retraceFrame(stackTraceLine, context);
     List<String> result = new ArrayList<>();
-    joinAmbiguousLines(listResultWithContext.getLines(), result::add);
-    return ResultWithContextImpl.create(result, listResultWithContext.getContext());
+    joinAmbiguousLines(listRetraceStackTraceResult.getAmbiguousResult(), result::add);
+    return RetraceStackFrameResultWithContextImpl.create(
+        result, listRetraceStackTraceResult.getContext());
   }
 
   /**
@@ -175,22 +170,21 @@
     RetraceStackTraceContext context = RetraceStackTraceContext.empty();
     String retraceLine;
     while ((retraceLine = lineSupplier.getNext()) != null) {
-      ResultWithContext<String> result = retrace(retraceLine, context);
+      RetraceStackFrameResultWithContext<String> result = retrace(retraceLine, context);
       context = result.getContext();
       result.forEach(lineConsumer);
     }
   }
 
   private void joinAmbiguousLines(
-      List<List<String>> retracedResult, Consumer<String> joinedConsumer) {
+      List<RetraceStackFrameResult<String>> retracedResult, Consumer<String> joinedConsumer) {
     if (retracedResult.isEmpty()) {
       // The result is empty, likely it maps to compiler synthesized items.
       return;
     }
     Set<String> reportedFrames = new HashSet<>();
-    ListUtils.forEachWithIndex(
-        retracedResult,
-        (potentialResults, index) -> {
+    retracedResult.forEach(
+        potentialResults -> {
           assert !potentialResults.isEmpty();
           // Check if we already reported position.
           if (reportedFrames.add(potentialResults.get(0))) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
index 6b050c6..b3012cf 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionKeyStrategy.java
@@ -9,7 +9,8 @@
   OBFUSCATED_TYPE_NAME_AS_KEY(0),
   OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS(1);
 
-  private static final MappingPartitionKeyStrategy DEFAULT_STRATEGY = OBFUSCATED_TYPE_NAME_AS_KEY;
+  private static final MappingPartitionKeyStrategy DEFAULT_STRATEGY =
+      OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS;
 
   private final int serializedKey;
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
similarity index 70%
rename from src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
rename to src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
index de94d1e..5a9fc41 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
@@ -13,9 +13,10 @@
 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.FieldReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.retrace.InvalidMappingFileException;
 import com.android.tools.r8.retrace.MappingPartitionFromKeySupplier;
-import com.android.tools.r8.retrace.PartitionMappingSupplier;
 import com.android.tools.r8.retrace.PrepareMappingPartitionsCallback;
 import com.android.tools.r8.retrace.RegisterMappingPartitionCallback;
 import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
@@ -27,17 +28,12 @@
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-/**
- * IntelliJ highlights the class as being invalid because it cannot see getClassNameMapper is
- * defined on the class for some reason.
- */
-public class PartitionMappingSupplierImpl extends PartitionMappingSupplier {
+public abstract class PartitionMappingSupplierBase<T extends PartitionMappingSupplierBase<T>> {
 
-  private final byte[] metadata;
-  private final RegisterMappingPartitionCallback registerPartitionCallback;
-  private final PrepareMappingPartitionsCallback prepare;
-  private final MappingPartitionFromKeySupplier partitionSupplier;
+  private final RegisterMappingPartitionCallback registerCallback;
+  private final PrepareMappingPartitionsCallback prepareCallback;
   private final boolean allowExperimental;
+  private final byte[] metadata;
   private final MapVersion fallbackMapVersion;
 
   private ClassNameMapper classNameMapper;
@@ -46,22 +42,20 @@
 
   private MappingPartitionMetadataInternal mappingPartitionMetadataCache;
 
-  PartitionMappingSupplierImpl(
-      byte[] metadata,
-      RegisterMappingPartitionCallback registerPartitionCallback,
-      PrepareMappingPartitionsCallback prepare,
-      MappingPartitionFromKeySupplier partitionSupplier,
+  protected PartitionMappingSupplierBase(
+      RegisterMappingPartitionCallback registerCallback,
+      PrepareMappingPartitionsCallback prepareCallback,
       boolean allowExperimental,
+      byte[] metadata,
       MapVersion fallbackMapVersion) {
-    this.metadata = metadata;
-    this.registerPartitionCallback = registerPartitionCallback;
-    this.prepare = prepare;
-    this.partitionSupplier = partitionSupplier;
+    this.registerCallback = registerCallback;
+    this.prepareCallback = prepareCallback;
     this.allowExperimental = allowExperimental;
+    this.metadata = metadata;
     this.fallbackMapVersion = fallbackMapVersion;
   }
 
-  private MappingPartitionMetadataInternal getMetadata(DiagnosticsHandler diagnosticsHandler) {
+  protected MappingPartitionMetadataInternal getMetadata(DiagnosticsHandler diagnosticsHandler) {
     if (mappingPartitionMetadataCache != null) {
       return mappingPartitionMetadataCache;
     }
@@ -70,38 +64,42 @@
             CompatByteBuffer.wrapOrNull(metadata), fallbackMapVersion, diagnosticsHandler);
   }
 
-  @Override
-  public PartitionMappingSupplier registerClassUse(
-      DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
-    registerKeyUse(getMetadata(diagnosticsHandler).getKey(classReference));
-    return this;
+  public T registerClassUse(DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
+    return registerKeyUse(classReference.getTypeName());
   }
 
-  private void registerKeyUse(String key) {
+  public T registerMethodUse(
+      DiagnosticsHandler diagnosticsHandler, MethodReference methodReference) {
+    return registerClassUse(diagnosticsHandler, methodReference.getHolderClass());
+  }
+
+  public T registerFieldUse(DiagnosticsHandler diagnosticsHandler, FieldReference fieldReference) {
+    return registerClassUse(diagnosticsHandler, fieldReference.getHolderClass());
+  }
+
+  public T registerKeyUse(String key) {
     // TODO(b/274735214): only call the register partition if we have a partition for it.
     if (!builtKeys.contains(key) && pendingKeys.add(key)) {
-      registerPartitionCallback.register(key);
+      registerCallback.register(key);
     }
+    return self();
   }
 
-  @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(
         getMetadata(diagnosticsHandler).getMapVersion().toMapVersionMappingInformation());
   }
 
-  @Override
-  public RetracerImpl createRetracer(DiagnosticsHandler diagnosticsHandler) {
-    MappingPartitionMetadataInternal metadata = getMetadata(diagnosticsHandler);
+  protected RetracerImpl createRetracerFromPartitionSupplier(
+      DiagnosticsHandler diagnosticsHandler, MappingPartitionFromKeySupplier partitionSupplier) {
     if (!pendingKeys.isEmpty()) {
-      prepare.prepare();
+      prepareCallback.prepare();
     }
     for (String pendingKey : pendingKeys) {
       try {
@@ -116,7 +114,7 @@
         classNameMapper =
             ClassNameMapper.mapperFromLineReaderWithFiltering(
                     reader,
-                    metadata.getMapVersion(),
+                    getMetadata(diagnosticsHandler).getMapVersion(),
                     diagnosticsHandler,
                     true,
                     allowExperimental,
@@ -134,4 +132,6 @@
     return RetracerImpl.createInternal(
         MappingSupplierInternalImpl.createInternal(classNameMapper), diagnosticsHandler);
   }
+
+  public abstract T self();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java
deleted file mode 100644
index 2f69645..0000000
--- a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBuilderImpl.java
+++ /dev/null
@@ -1,78 +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.internal;
-
-import com.android.tools.r8.naming.MapVersion;
-import com.android.tools.r8.retrace.MappingPartitionFromKeySupplier;
-import com.android.tools.r8.retrace.PartitionMappingSupplier;
-import com.android.tools.r8.retrace.PrepareMappingPartitionsCallback;
-import com.android.tools.r8.retrace.RegisterMappingPartitionCallback;
-
-public class PartitionMappingSupplierBuilderImpl extends PartitionMappingSupplier.Builder {
-
-  private MappingPartitionFromKeySupplier partitionSupplier;
-  private RegisterMappingPartitionCallback registerPartitionCallback = key -> {};
-  private PrepareMappingPartitionsCallback prepare = () -> {};
-  private byte[] metadata;
-  private final MapVersion fallbackMapVersion;
-  private boolean allowExperimental = false;
-
-  public PartitionMappingSupplierBuilderImpl(MapVersion fallbackMapVersion) {
-    this.fallbackMapVersion = fallbackMapVersion;
-  }
-
-  @Override
-  public PartitionMappingSupplier.Builder self() {
-    return this;
-  }
-
-  @Override
-  public PartitionMappingSupplier.Builder setAllowExperimental(boolean allowExperimental) {
-    this.allowExperimental = allowExperimental;
-    return self();
-  }
-
-  @Override
-  public PartitionMappingSupplier.Builder setMetadata(byte[] metadata) {
-    this.metadata = metadata;
-    return self();
-  }
-
-  @Override
-  public PartitionMappingSupplier.Builder setRegisterMappingPartitionCallback(
-      RegisterMappingPartitionCallback registerPartitionCallback) {
-    this.registerPartitionCallback = registerPartitionCallback;
-    return self();
-  }
-
-  @Override
-  public PartitionMappingSupplier.Builder setPrepareMappingPartitionsCallback(
-      PrepareMappingPartitionsCallback prepare) {
-    this.prepare = prepare;
-    return self();
-  }
-
-  @Override
-  public PartitionMappingSupplier.Builder setMappingPartitionFromKeySupplier(
-      MappingPartitionFromKeySupplier partitionSupplier) {
-    this.partitionSupplier = partitionSupplier;
-    return self();
-  }
-
-  @Override
-  public PartitionMappingSupplier build() {
-    if (partitionSupplier == null) {
-      throw new RuntimeException(
-          "Cannot build without providing a mapping partition from key supplier.");
-    }
-    return new PartitionMappingSupplierImpl(
-        metadata,
-        registerPartitionCallback,
-        prepare,
-        partitionSupplier,
-        allowExperimental,
-        fallbackMapVersion);
-  }
-}
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 f2d647a..8fe45e5 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
@@ -12,6 +12,8 @@
 import com.android.tools.r8.naming.ProguardMapChecker.VerifyMappingFileHashResult;
 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.retrace.InvalidMappingFileException;
 import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.ProguardMappingSupplier;
@@ -72,6 +74,18 @@
   }
 
   @Override
+  public ProguardMappingSupplier registerMethodUse(
+      DiagnosticsHandler diagnosticsHandler, MethodReference methodReference) {
+    return registerClassUse(diagnosticsHandler, methodReference.getHolderClass());
+  }
+
+  @Override
+  public ProguardMappingSupplier registerFieldUse(
+      DiagnosticsHandler diagnosticsHandler, FieldReference fieldReference) {
+    return registerClassUse(diagnosticsHandler, fieldReference.getHolderClass());
+  }
+
+  @Override
   public void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler) {
     try (InputStream reader = proguardMapProducer.get()) {
       VerifyMappingFileHashResult checkResult =
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.java
deleted file mode 100644
index e65ecd5..0000000
--- a/src/main/java/com/android/tools/r8/retrace/internal/ResultWithContextImpl.java
+++ /dev/null
@@ -1,45 +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.internal;
-
-import com.android.tools.r8.retrace.ResultWithContext;
-import com.android.tools.r8.retrace.RetraceStackTraceContext;
-import java.util.List;
-import java.util.function.Consumer;
-
-public class ResultWithContextImpl<T> implements ResultWithContext<T> {
-
-  private final List<T> result;
-  private final RetraceStackTraceContext context;
-
-  private ResultWithContextImpl(List<T> result, RetraceStackTraceContext context) {
-    this.result = result;
-    this.context = context;
-  }
-
-  public static <T> ResultWithContext<T> create(List<T> result, RetraceStackTraceContext context) {
-    return new ResultWithContextImpl<>(result, context);
-  }
-
-  @Override
-  public RetraceStackTraceContext getContext() {
-    return context;
-  }
-
-  @Override
-  public List<T> getLines() {
-    return result;
-  }
-
-  @Override
-  public void forEach(Consumer<T> consumer) {
-    result.forEach(consumer);
-  }
-
-  @Override
-  public boolean isEmpty() {
-    return result.isEmpty();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceBase.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceBase.java
new file mode 100644
index 0000000..7820fce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceBase.java
@@ -0,0 +1,322 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.retrace.MappingSupplierBase;
+import com.android.tools.r8.retrace.RetraceInvalidStackTraceLineDiagnostics;
+import com.android.tools.r8.retrace.RetraceStackFrameAmbiguousResult;
+import com.android.tools.r8.retrace.RetraceStackFrameAmbiguousResultWithContext;
+import com.android.tools.r8.retrace.RetraceStackFrameResult;
+import com.android.tools.r8.retrace.RetraceStackFrameResultWithContext;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
+import com.android.tools.r8.retrace.RetraceStackTraceElementProxyResult;
+import com.android.tools.r8.retrace.RetraceStackTraceResult;
+import com.android.tools.r8.retrace.RetracedFieldReference;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedTypeReference;
+import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.retrace.StackTraceElementProxy;
+import com.android.tools.r8.retrace.StackTraceElementProxyRetracer;
+import com.android.tools.r8.retrace.StackTraceLineParser;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class RetraceBase<T, ST extends StackTraceElementProxy<T, ST>> {
+
+  private final StackTraceLineParser<T, ST> stackTraceLineParser;
+  private final MappingSupplierBase<?> mappingSupplier;
+  private final DiagnosticsHandler diagnosticsHandler;
+  protected final boolean isVerbose;
+
+  protected RetraceBase(
+      StackTraceLineParser<T, ST> stackTraceLineParser,
+      MappingSupplierBase<?> mappingSupplier,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean isVerbose) {
+    this.stackTraceLineParser = stackTraceLineParser;
+    this.mappingSupplier = mappingSupplier;
+    this.diagnosticsHandler = diagnosticsHandler;
+    this.isVerbose = isVerbose;
+  }
+
+  protected List<ST> parse(List<T> stackTrace) {
+    ListUtils.forEachWithIndex(
+        stackTrace,
+        (line, lineNumber) -> {
+          if (line == null) {
+            diagnosticsHandler.error(
+                RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
+            throw new RetraceAbortException();
+          }
+        });
+    return ListUtils.map(stackTrace, stackTraceLineParser::parse);
+  }
+
+  protected ST parse(T obfuscated) {
+    return stackTraceLineParser.parse(obfuscated);
+  }
+
+  protected void registerUses(List<ST> parsed) {
+    parsed.forEach(this::registerUses);
+  }
+
+  protected void registerUses(ST parsed) {
+    parsed.registerUses(mappingSupplier, diagnosticsHandler);
+  }
+
+  protected RetraceStackTraceResult<T> retraceStackTraceParsedWithRetracer(
+      Retracer retracer, List<ST> stackTrace, RetraceStackTraceContext context) {
+    RetraceStackTraceElementProxyEquivalence<T, ST> equivalence =
+        new RetraceStackTraceElementProxyEquivalence<>(isVerbose);
+    StackTraceElementProxyRetracer<T, ST> proxyRetracer =
+        StackTraceElementProxyRetracer.createDefault(retracer);
+    List<RetraceStackFrameAmbiguousResult<T>> finalResult = new ArrayList<>();
+    RetraceStackTraceContext finalContext =
+        ListUtils.fold(
+            stackTrace,
+            context,
+            (newContext, stackTraceLine) -> {
+              List<Pair<RetraceStackTraceElementProxy<T, ST>, RetraceStackFrameResult<T>>>
+                  resultsForLine = new ArrayList<>();
+              Box<List<T>> currentList = new Box<>();
+              Set<Wrapper<RetraceStackTraceElementProxy<T, ST>>> seen = new HashSet<>();
+              List<RetraceStackTraceContext> contexts = new ArrayList<>();
+              RetraceStackTraceElementProxyResult<T, ST> retraceResult =
+                  proxyRetracer.retrace(stackTraceLine, newContext);
+              retraceResult.stream()
+                  .forEach(
+                      retracedElement -> {
+                        if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
+                          if (seen.add(equivalence.wrap(retracedElement))) {
+                            currentList.set(new ArrayList<>());
+                            resultsForLine.add(
+                                Pair.create(
+                                    retracedElement,
+                                    RetraceStackFrameResultWithContextImpl.create(
+                                        currentList.get(), RetraceStackTraceContext.empty())));
+                            contexts.add(retracedElement.getContext());
+                          } else {
+                            currentList.clear();
+                          }
+                        }
+                        if (currentList.isSet()) {
+                          currentList
+                              .get()
+                              .add(stackTraceLine.toRetracedItem(retracedElement, isVerbose));
+                        }
+                      });
+              resultsForLine.sort(Comparator.comparing(Pair::getFirst));
+              finalResult.add(
+                  RetraceStackFrameAmbiguousResultWithContextImpl.create(
+                      ListUtils.map(resultsForLine, Pair::getSecond),
+                      RetraceStackTraceContext.empty()));
+              if (contexts.isEmpty()) {
+                return retraceResult.getResultContext();
+              }
+              return contexts.size() == 1 ? contexts.get(0) : RetraceStackTraceContext.empty();
+            });
+    return RetraceStackTraceResultImpl.create(finalResult, finalContext);
+  }
+
+  protected RetraceStackFrameAmbiguousResultWithContext<T> retraceFrameWithRetracer(
+      Retracer retracer, ST parsedFrame, RetraceStackTraceContext context) {
+    Map<RetraceStackTraceElementProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
+    List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
+    StackTraceElementProxyRetracer<T, ST> proxyRetracer =
+        StackTraceElementProxyRetracer.createDefault(retracer);
+    Box<RetraceStackTraceContext> contextBox = new Box<>(context);
+    proxyRetracer.retrace(parsedFrame, context).stream()
+        .forEach(
+            retracedElement -> {
+              if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
+                ambiguousKeys.add(retracedElement);
+                ambiguousBlocks.put(retracedElement, new ArrayList<>());
+              }
+              ambiguousBlocks
+                  .get(ListUtils.last(ambiguousKeys))
+                  .add(parsedFrame.toRetracedItem(retracedElement, isVerbose));
+              contextBox.set(retracedElement.getContext());
+            });
+    Collections.sort(ambiguousKeys);
+    List<RetraceStackFrameResult<T>> retracedList = new ArrayList<>();
+    ambiguousKeys.forEach(
+        key ->
+            retracedList.add(
+                RetraceStackFrameResultWithContextImpl.create(
+                    ambiguousBlocks.get(key), RetraceStackTraceContext.empty())));
+    return RetraceStackFrameAmbiguousResultWithContextImpl.create(retracedList, contextBox.get());
+  }
+
+  protected RetraceStackFrameResultWithContext<T> retraceLineWithRetracer(
+      Retracer retracer, ST parsedFrame, RetraceStackTraceContext context) {
+    StackTraceElementProxyRetracer<T, ST> proxyRetracer =
+        StackTraceElementProxyRetracer.createDefault(retracer);
+    Box<RetraceStackTraceContext> contextBox = new Box<>(context);
+    List<T> result =
+        proxyRetracer.retrace(parsedFrame, context).stream()
+            .map(
+                retraceFrame -> {
+                  contextBox.set(retraceFrame.getContext());
+                  return parsedFrame.toRetracedItem(retraceFrame, isVerbose);
+                })
+            .collect(Collectors.toList());
+    return RetraceStackFrameResultWithContextImpl.create(result, contextBox.get());
+  }
+
+  private static class RetraceStackTraceElementProxyEquivalence<
+          T, ST extends StackTraceElementProxy<T, ST>>
+      extends Equivalence<RetraceStackTraceElementProxy<T, ST>> {
+
+    private final boolean isVerbose;
+
+    public RetraceStackTraceElementProxyEquivalence(boolean isVerbose) {
+      this.isVerbose = isVerbose;
+    }
+
+    @Override
+    protected boolean doEquivalent(
+        RetraceStackTraceElementProxy<T, ST> one, RetraceStackTraceElementProxy<T, ST> other) {
+      if (one == other) {
+        return true;
+      }
+      if (testNotEqualProperty(
+              one,
+              other,
+              RetraceStackTraceElementProxy::hasRetracedClass,
+              r -> r.getRetracedClass().getTypeName())
+          || testNotEqualProperty(
+              one,
+              other,
+              RetraceStackTraceElementProxy::hasSourceFile,
+              RetraceStackTraceElementProxy::getSourceFile)) {
+        return false;
+      }
+      assert one.getOriginalItem() == other.getOriginalItem();
+      if (isVerbose
+          || (one.getOriginalItem().hasLineNumber() && one.getOriginalItem().getLineNumber() > 0)) {
+        if (testNotEqualProperty(
+            one,
+            other,
+            RetraceStackTraceElementProxy::hasLineNumber,
+            RetraceStackTraceElementProxy::getLineNumber)) {
+          return false;
+        }
+      }
+      if (one.hasRetracedMethod() != other.hasRetracedMethod()) {
+        return false;
+      }
+      if (one.hasRetracedMethod()) {
+        RetracedMethodReference oneMethod = one.getRetracedMethod();
+        RetracedMethodReference otherMethod = other.getRetracedMethod();
+        if (oneMethod.isKnown() != otherMethod.isKnown()) {
+          return false;
+        }
+        // In verbose mode we check the signature, otherwise we only check the name
+        if (!oneMethod.getMethodName().equals(otherMethod.getMethodName())) {
+          return false;
+        }
+        if (isVerbose
+            && ((oneMethod.isKnown()
+                    && !oneMethod
+                        .asKnown()
+                        .getMethodReference()
+                        .toString()
+                        .equals(otherMethod.asKnown().getMethodReference().toString()))
+                || (!oneMethod.isKnown()
+                    && !oneMethod.getMethodName().equals(otherMethod.getMethodName())))) {
+          return false;
+        }
+      }
+      if (one.hasRetracedField() != other.hasRetracedField()) {
+        return false;
+      }
+      if (one.hasRetracedField()) {
+        RetracedFieldReference oneField = one.getRetracedField();
+        RetracedFieldReference otherField = other.getRetracedField();
+        if (oneField.isKnown() != otherField.isKnown()) {
+          return false;
+        }
+        if (!oneField.getFieldName().equals(otherField.getFieldName())) {
+          return false;
+        }
+        if (isVerbose
+            && ((oneField.isKnown()
+                    && !oneField
+                        .asKnown()
+                        .getFieldReference()
+                        .toString()
+                        .equals(otherField.asKnown().getFieldReference().toString()))
+                || (oneField.isUnknown()
+                    && !oneField.getFieldName().equals(otherField.getFieldName())))) {
+          return false;
+        }
+      }
+      if (one.hasRetracedFieldOrReturnType() != other.hasRetracedFieldOrReturnType()) {
+        return false;
+      }
+      if (one.hasRetracedFieldOrReturnType()) {
+        RetracedTypeReference oneFieldOrReturn = one.getRetracedFieldOrReturnType();
+        RetracedTypeReference otherFieldOrReturn = other.getRetracedFieldOrReturnType();
+        if (!compareRetracedTypeReference(oneFieldOrReturn, otherFieldOrReturn)) {
+          return false;
+        }
+      }
+      if (one.hasRetracedMethodArguments() != other.hasRetracedMethodArguments()) {
+        return false;
+      }
+      if (one.hasRetracedMethodArguments()) {
+        List<RetracedTypeReference> oneMethodArguments = one.getRetracedMethodArguments();
+        List<RetracedTypeReference> otherMethodArguments = other.getRetracedMethodArguments();
+        if (oneMethodArguments.size() != otherMethodArguments.size()) {
+          return false;
+        }
+        for (int i = 0; i < oneMethodArguments.size(); i++) {
+          if (compareRetracedTypeReference(
+              oneMethodArguments.get(i), otherMethodArguments.get(i))) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    private boolean compareRetracedTypeReference(
+        RetracedTypeReference one, RetracedTypeReference other) {
+      return one.isVoid() == other.isVoid()
+          && (one.isVoid() || one.getTypeName().equals(other.getTypeName()));
+    }
+
+    @Override
+    protected int doHash(RetraceStackTraceElementProxy<T, ST> proxy) {
+      return 0;
+    }
+
+    private <V extends Comparable<V>> boolean testNotEqualProperty(
+        RetraceStackTraceElementProxy<T, ST> one,
+        RetraceStackTraceElementProxy<T, ST> other,
+        Function<RetraceStackTraceElementProxy<T, ST>, Boolean> predicate,
+        Function<RetraceStackTraceElementProxy<T, ST>, V> getter) {
+      return Comparator.comparing(predicate)
+              .thenComparing(getter, Comparator.nullsFirst(V::compareTo))
+              .compare(one, other)
+          != 0;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackFrameAmbiguousResultWithContextImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackFrameAmbiguousResultWithContextImpl.java
new file mode 100644
index 0000000..cabb3f7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackFrameAmbiguousResultWithContextImpl.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.retrace.RetraceStackFrameAmbiguousResultWithContext;
+import com.android.tools.r8.retrace.RetraceStackFrameResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class RetraceStackFrameAmbiguousResultWithContextImpl<T>
+    implements RetraceStackFrameAmbiguousResultWithContext<T> {
+
+  private final RetraceStackTraceContext context;
+  private final List<RetraceStackFrameResult<T>> result;
+
+  private RetraceStackFrameAmbiguousResultWithContextImpl(
+      List<RetraceStackFrameResult<T>> result, RetraceStackTraceContext context) {
+    this.result = result;
+    this.context = context;
+  }
+
+  public static <T> RetraceStackFrameAmbiguousResultWithContextImpl<T> create(
+      List<RetraceStackFrameResult<T>> result, RetraceStackTraceContext context) {
+    return new RetraceStackFrameAmbiguousResultWithContextImpl<>(result, context);
+  }
+
+  @Override
+  public RetraceStackTraceContext getContext() {
+    return context;
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    return result.size() > 1;
+  }
+
+  @Override
+  public List<RetraceStackFrameResult<T>> getAmbiguousResult() {
+    return result;
+  }
+
+  @Override
+  public void forEach(Consumer<RetraceStackFrameResult<T>> consumer) {
+    result.forEach(consumer);
+  }
+
+  @Override
+  public void forEachWithIndex(BiConsumer<RetraceStackFrameResult<T>, Integer> consumer) {
+    for (int i = 0; i < result.size(); i++) {
+      consumer.accept(result.get(i), i);
+    }
+  }
+
+  @Override
+  public int size() {
+    return result.size();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return result.isEmpty();
+  }
+
+  @Override
+  public RetraceStackFrameResult<T> get(int i) {
+    return result.get(i);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackFrameResultWithContextImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackFrameResultWithContextImpl.java
new file mode 100644
index 0000000..974d80f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackFrameResultWithContextImpl.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.retrace.RetraceStackFrameResultWithContext;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class RetraceStackFrameResultWithContextImpl<T>
+    implements RetraceStackFrameResultWithContext<T> {
+
+  private final RetraceStackTraceContext context;
+  private final List<T> result;
+
+  private RetraceStackFrameResultWithContextImpl(List<T> result, RetraceStackTraceContext context) {
+    this.result = result;
+    this.context = context;
+  }
+
+  public static <T> RetraceStackFrameResultWithContextImpl<T> create(
+      List<T> result, RetraceStackTraceContext context) {
+    return new RetraceStackFrameResultWithContextImpl<>(result, context);
+  }
+
+  @Override
+  public RetraceStackTraceContext getContext() {
+    return context;
+  }
+
+  @Override
+  public List<T> getResult() {
+    return result;
+  }
+
+  @Override
+  public void forEach(Consumer<T> consumer) {
+    result.forEach(consumer);
+  }
+
+  @Override
+  public int size() {
+    return result.size();
+  }
+
+  @Override
+  public T get(int i) {
+    return result.get(i);
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return result.isEmpty();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceResultImpl.java
new file mode 100644
index 0000000..4a4ea6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceResultImpl.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.retrace.RetraceStackFrameAmbiguousResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceStackTraceResult;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class RetraceStackTraceResultImpl<T> implements RetraceStackTraceResult<T> {
+
+  private final List<RetraceStackFrameAmbiguousResult<T>> result;
+  private final RetraceStackTraceContext context;
+
+  private RetraceStackTraceResultImpl(
+      List<RetraceStackFrameAmbiguousResult<T>> result, RetraceStackTraceContext context) {
+    this.result = result;
+    this.context = context;
+  }
+
+  public static <T> RetraceStackTraceResult<T> create(
+      List<RetraceStackFrameAmbiguousResult<T>> result, RetraceStackTraceContext context) {
+    return new RetraceStackTraceResultImpl<>(result, context);
+  }
+
+  @Override
+  public RetraceStackTraceContext getContext() {
+    return context;
+  }
+
+  @Override
+  public List<RetraceStackFrameAmbiguousResult<T>> getResult() {
+    return result;
+  }
+
+  @Override
+  public void forEach(Consumer<RetraceStackFrameAmbiguousResult<T>> consumer) {
+    getResult().forEach(consumer);
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return result.isEmpty();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
index b73c9bc..0e01d09 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
@@ -300,7 +300,7 @@
         boolean insertSeparatorForRetraced = false;
         // We need to include ':' in the group since we may want to rewrite '(SourceFile:0)` into
         // (SourceFile) and not (SourceFile:)
-        if (startOfGroup > 0 && builder.getLine().charAt(startOfGroup + -1) == ':') {
+        if (startOfGroup > 0 && builder.getLine().charAt(startOfGroup - 1) == ':') {
           startOfGroup = startOfGroup - 1;
           insertSeparatorForRetraced = true;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilderEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilderEventConsumer.java
index b59961e..6bf84a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilderEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilderEventConsumer.java
@@ -31,7 +31,7 @@
     }
 
     @Override
-    public void acceptCompanionClassClinit(ProgramMethod method) {
+    public void acceptCompanionClassClinit(ProgramMethod method, ProgramMethod companionMethod) {
       // Intentionally empty.
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 9b437c1..150e7c8 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -56,10 +56,10 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.UseRegistryWithResult;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
 import com.android.tools.r8.graph.lens.FieldLookupResult;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.MethodLookupResult;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 68c2918..c955fad 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.graph.fixup.TreeFixerBase;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.NestedGraphLens;
 import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
index 653e567..8142f7b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.utils.MapConsumerUtils.wrapExistingMapConsumer;
+
 import com.android.tools.r8.BaseCompilerCommand;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
@@ -14,10 +16,9 @@
 import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.ProguardMapConsumer;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.naming.MultiProguardMapConsumer;
+import com.android.tools.r8.naming.MapConsumer;
 import com.android.tools.r8.naming.ProguardMapStringConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.google.common.io.ByteStreams;
@@ -36,20 +37,19 @@
   private boolean closed = false;
 
   private ProgramConsumer programConsumer = null;
-  private ProguardMapConsumer proguardMapConsumer = null;
+  private MapConsumer mapConsumer = null;
 
   public AndroidAppConsumers() {
     // Nothing to do.
   }
 
-  public AndroidAppConsumers(BaseCompilerCommand.Builder builder) {
+  public AndroidAppConsumers(BaseCompilerCommand.Builder<?, ?> builder) {
     builder.setProgramConsumer(wrapProgramConsumer(builder.getProgramConsumer()));
   }
 
   public AndroidAppConsumers(InternalOptions options) {
     options.programConsumer = wrapProgramConsumer(options.programConsumer);
-    options.proguardMapConsumer =
-        wrapProguardMapConsumer(options.proguardMapConsumer, options.reporter);
+    options.mapConsumer = wrapProguardMapConsumer(options.mapConsumer);
   }
 
   public ProgramConsumer wrapProgramConsumer(ProgramConsumer consumer) {
@@ -69,39 +69,35 @@
     return programConsumer;
   }
 
-  public ProguardMapConsumer wrapProguardMapConsumer(
-      ProguardMapConsumer consumer, DiagnosticsHandler diagnosticsHandler) {
-    assert proguardMapConsumer == null;
+  public MapConsumer wrapProguardMapConsumer(MapConsumer consumer) {
+    assert mapConsumer == null;
     if (consumer != null) {
-      proguardMapConsumer =
-          MultiProguardMapConsumer.builder()
-              .addProguardMapConsumer(consumer)
-              .addProguardMapConsumer(
-                  ProguardMapStringConsumer.builder()
-                      .setStringConsumer(
-                          new StringConsumer() {
-                            StringBuilder stringBuilder = null;
+      mapConsumer =
+          wrapExistingMapConsumer(
+              consumer,
+              ProguardMapStringConsumer.builder()
+                  .setStringConsumer(
+                      new StringConsumer() {
+                        StringBuilder stringBuilder = null;
 
-                            @Override
-                            public void accept(String string, DiagnosticsHandler handler) {
-                              if (stringBuilder == null) {
-                                stringBuilder = new StringBuilder();
-                              }
-                              stringBuilder.append(string);
-                            }
+                        @Override
+                        public void accept(String string, DiagnosticsHandler handler) {
+                          if (stringBuilder == null) {
+                            stringBuilder = new StringBuilder();
+                          }
+                          stringBuilder.append(string);
+                        }
 
-                            @Override
-                            public void finished(DiagnosticsHandler handler) {
-                              if (stringBuilder != null) {
-                                builder.setProguardMapOutputData(stringBuilder.toString());
-                              }
-                            }
-                          })
-                      .setDiagnosticsHandler(diagnosticsHandler)
-                      .build())
-              .build();
+                        @Override
+                        public void finished(DiagnosticsHandler handler) {
+                          if (stringBuilder != null) {
+                            builder.setProguardMapOutputData(stringBuilder.toString());
+                          }
+                        }
+                      })
+                  .build());
     }
-    return proguardMapConsumer;
+    return mapConsumer;
   }
 
   public DexIndexedConsumer wrapDexIndexedConsumer(DexIndexedConsumer consumer) {
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
index 00fde15..621d108 100644
--- a/src/main/java/com/android/tools/r8/utils/Box.java
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -32,6 +32,13 @@
     return super.computeIfAbsent(supplier);
   }
 
+  public <E extends Exception> T computeIfAbsentThrowing(ThrowingSupplier<T, E> supplier) throws E {
+    if (!isSet()) {
+      set(supplier.get());
+    }
+    return get();
+  }
+
   @Override
   public T get() {
     return super.get();
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 67f4fda..cd3c1c5 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.GlobalSyntheticsConsumer;
 import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.ProguardMapConsumer;
 import com.android.tools.r8.SourceFileProvider;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.SyntheticInfoConsumer;
@@ -72,6 +71,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MapConsumer;
 import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
 import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalOptions;
@@ -1051,7 +1051,7 @@
 
   // If null, no proguard map needs to be computed.
   // If non null it must be and passed to the consumer.
-  public ProguardMapConsumer proguardMapConsumer = null;
+  public MapConsumer mapConsumer = null;
 
   // If null, no usage information needs to be computed.
   // If non-null, it must be and is passed to the consumer.
diff --git a/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
new file mode 100644
index 0000000..5f8d295
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.PartitionMapConsumer;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MapConsumer;
+import com.android.tools.r8.naming.ProguardMapMarkerInfo;
+import com.android.tools.r8.retrace.MappingPartition;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.function.Function;
+
+public class MapConsumerUtils {
+
+  public static MapConsumer wrapExistingMapConsumer(
+      MapConsumer existingMapConsumer, MapConsumer newConsumer) {
+    if (existingMapConsumer == null) {
+      return newConsumer;
+    }
+    return new MapConsumer() {
+      @Override
+      public void accept(
+          DiagnosticsHandler diagnosticsHandler,
+          ProguardMapMarkerInfo makerInfo,
+          ClassNameMapper classNameMapper) {
+        existingMapConsumer.accept(diagnosticsHandler, makerInfo, classNameMapper);
+        newConsumer.accept(diagnosticsHandler, makerInfo, classNameMapper);
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        existingMapConsumer.finished(handler);
+        newConsumer.finished(handler);
+      }
+    };
+  }
+
+  public static <T> MapConsumer wrapExistingMapConsumerIfNotNull(
+      MapConsumer existingMapConsumer, T object, Function<T, MapConsumer> producer) {
+    if (object == null) {
+      return existingMapConsumer;
+    }
+    return wrapExistingMapConsumer(existingMapConsumer, producer.apply(object));
+  }
+
+  public static PartitionMapConsumer createZipConsumer(Path path) {
+    return new PartitionMapConsumer() {
+
+      private final Box<ZipBuilder> zipBuilderBox = new Box<>();
+
+      @Override
+      public void acceptMappingPartition(MappingPartition mappingPartition) {
+        try {
+          zipBuilderBox
+              .computeIfAbsentThrowing(() -> ZipBuilder.builder(path))
+              .addBytes(mappingPartition.getKey(), mappingPartition.getPayload());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+
+      @Override
+      public void acceptMappingPartitionMetadata(
+          MappingPartitionMetadata mappingPartitionMetadata) {
+        try {
+          zipBuilderBox
+              .computeIfAbsentThrowing(() -> ZipBuilder.builder(path))
+              .addBytes("METADATA", mappingPartitionMetadata.getBytes());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        try {
+          zipBuilderBox.computeIfAbsentThrowing(() -> ZipBuilder.builder(path)).build();
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 81578f8..604695c 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -168,7 +168,7 @@
           appView.graphLens().getRenamedMethodSignature(method, appliedGraphLens);
       DexProgramClass holder = appView.definitionForHolder(rewrittenMethod).asProgramClass();
       DexEncodedMethod definition = holder.lookupMethod(rewrittenMethod);
-      assert definition != null;
+      assert definition != null : "Missing method: " + rewrittenMethod;
       result.createAndAdd(holder, definition);
     }
     return result;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
index 0181e06..881fbe2 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -46,7 +46,7 @@
       Timing timing,
       OriginalSourceFiles originalSourceFiles,
       DebugRepresentationPredicate representation) {
-    assert appView.options().proguardMapConsumer != null;
+    assert appView.options().mapConsumer != null;
     if (shouldEmitOriginalMappingFile(appView)) {
       appView.options().reporter.warning(new NotSupportedMapVersionForMappingComposeDiagnostic());
       timing.begin("Write proguard map");
diff --git a/src/test/examples/catchhandleroverlap/CatchHandlerOverlap.java b/src/test/examples/catchhandleroverlap/CatchHandlerOverlap.java
deleted file mode 100644
index 7a4950c..0000000
--- a/src/test/examples/catchhandleroverlap/CatchHandlerOverlap.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package catchhandleroverlap;
-
-public class CatchHandlerOverlap {
-  private static void f() throws Exception {
-    throw new Exception("f");
-  }
-
-  private static void g() throws Exception {
-    throw new Exception("g");
-  }
-
-  private static void h(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9,
-      int i10, int i11, int i12, int i13, int i14, int i15, int i16, int i17) {
-    System.out.println(i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 +
-        i12 + i13 + i14 + i15 + i16 + i17);
-    try {
-      f();
-    } catch (Exception e0) {
-      try {
-        g();
-      } catch (Exception e1) {
-        System.out.println(e0.getMessage() + " " + e1.getMessage());
-      }
-    }
-  }
-
-  public static void main(String[] args) {
-    h(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
-  }
-}
diff --git a/src/test/examplesJava17/enum_sealed/Enum.java b/src/test/examplesJava17/enum_sealed/Enum.java
new file mode 100644
index 0000000..7483363
--- /dev/null
+++ b/src/test/examplesJava17/enum_sealed/Enum.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package enum_sealed;
+
+public enum Enum {
+  A,
+  B {
+    @Override
+    public String toString() {
+      return "a B";
+    }
+  }
+}
diff --git a/src/test/examplesJava17/enum_sealed/Main.java b/src/test/examplesJava17/enum_sealed/Main.java
new file mode 100644
index 0000000..8a4d42a
--- /dev/null
+++ b/src/test/examplesJava17/enum_sealed/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package enum_sealed;
+
+public class Main {
+
+  public static void main(String[] args) {
+    System.out.println(Enum.A);
+    System.out.println(Enum.B);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
deleted file mode 100644
index 95dd9bd..0000000
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ /dev/null
@@ -1,437 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static com.google.common.io.ByteStreams.toByteArray;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-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;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.util.ASMifier;
-import org.objectweb.asm.util.TraceClassVisitor;
-
-@RunWith(Parameterized.class)
-public class CfFrontendExamplesTest extends TestBase {
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public CfFrontendExamplesTest(TestParameters parameters) {
-    parameters.assertNoneRuntime();
-  }
-
-  @Test
-  public void testArithmetic() throws Exception {
-    runTest("arithmetic.Arithmetic");
-  }
-
-  @Test
-  public void testBridgeMethod() throws Exception {
-    runTest("bridge.BridgeMethod");
-  }
-
-  @Test
-  public void testCommonSubexpressionElimination() throws Exception {
-    runTest("cse.CommonSubexpressionElimination");
-  }
-
-  @Test
-  public void testConstants() throws Exception {
-    runTest("constants.Constants");
-  }
-
-  @Test
-  public void testControlFlow() throws Exception {
-    runTest("controlflow.ControlFlow");
-  }
-
-  @Test
-  public void testConversions() throws Exception {
-    runTest("conversions.Conversions");
-  }
-
-  @Test
-  public void testFloatingPointValuedAnnotation() throws Exception {
-    runTest("floating_point_annotations.FloatingPointValuedAnnotationTest");
-  }
-
-  @Test
-  public void testFilledArray() throws Exception {
-    runTest("filledarray.FilledArray");
-  }
-
-  @Test
-  public void testHello() throws Exception {
-    runTest("hello.Hello");
-  }
-
-  @Test
-  public void testIfStatements() throws Exception {
-    runTest("ifstatements.IfStatements");
-  }
-
-  @Test
-  public void testInlining() throws Exception {
-    runTest("inlining.Inlining");
-  }
-
-  @Test
-  public void testInstanceVariable() throws Exception {
-    runTest("instancevariable.InstanceVariable");
-  }
-
-  @Test
-  public void testInstanceofString() throws Exception {
-    runTest("instanceofstring.InstanceofString");
-  }
-
-  @Test
-  public void testInvoke() throws Exception {
-    runTest("invoke.Invoke");
-  }
-
-  @Test
-  public void testJumboString() throws Exception {
-    runTest("jumbostring.JumboString");
-  }
-
-  @Test
-  public void testLoadConst() throws Exception {
-    runTest("loadconst.LoadConst");
-  }
-
-  @Test
-  public void testUdpServer() throws Exception {
-    runTest("loop.UdpServer");
-  }
-
-  @Test
-  public void testRegAlloc() throws Exception {
-    runTest("regalloc.RegAlloc");
-  }
-
-  @Test
-  public void testReturns() throws Exception {
-    runTest("returns.Returns");
-  }
-
-  @Test
-  public void testStaticField() throws Exception {
-    runTest("staticfield.StaticField");
-  }
-
-  @Test
-  public void testStringBuilding() throws Exception {
-    runTest("stringbuilding.StringBuilding");
-  }
-
-  @Test
-  public void testSwitches() throws Exception {
-    runTest("switches.Switches");
-  }
-
-  @Test
-  public void testSync() throws Exception {
-    runTest("sync.Sync");
-  }
-
-  @Test
-  public void testThrowing() throws Exception {
-    runTest("throwing.Throwing");
-  }
-
-  @Test
-  public void testTrivial() throws Exception {
-    runTest("trivial.Trivial");
-  }
-
-  @Test
-  public void testTryCatch() throws Exception {
-    runTest("trycatch.TryCatch");
-  }
-
-  @Test
-  public void testNestedTryCatches() throws Exception {
-    runTest("nestedtrycatches.NestedTryCatches");
-  }
-
-  @Test
-  public void testTryCatchMany() throws Exception {
-    runTest("trycatchmany.TryCatchMany");
-  }
-
-  @Test
-  public void testInvokeEmpty() throws Exception {
-    runTest("invokeempty.InvokeEmpty");
-  }
-
-  @Test
-  public void testRegress() throws Exception {
-    runTest("regress.Regress");
-  }
-
-  @Test
-  public void testRegress2() throws Exception {
-    runTest("regress2.Regress2");
-  }
-
-  @Test
-  public void testRegress37726195() throws Exception {
-    runTest("regress_37726195.Regress");
-  }
-
-  @Test
-  public void testRegress37658666() throws Exception {
-    runTest(
-        "regress_37658666.Regress",
-        (expected, actual) -> {
-          // javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
-          String ldc = "methodVisitor.visitLdcInsn(new Float(\"-0.0\"));";
-          String constNeg = "methodVisitor.visitInsn(FCONST_0);\nmethodVisitor.visitInsn(FNEG);";
-          assertEquals(expected.replace(ldc, constNeg), actual);
-        });
-  }
-
-  @Test
-  public void testRegress37875803() throws Exception {
-    runTest("regress_37875803.Regress");
-  }
-
-  @Test
-  public void testRegress37955340() throws Exception {
-    runTest("regress_37955340.Regress");
-  }
-
-  @Test
-  public void testRegress62300145() throws Exception {
-    runTest("regress_62300145.Regress");
-  }
-
-  @Test
-  public void testRegress64881691() throws Exception {
-    runTest("regress_64881691.Regress");
-  }
-
-  @Test
-  public void testRegress65104300() throws Exception {
-    runTest("regress_65104300.Regress");
-  }
-
-  @Test
-  public void testRegress70703087() throws Exception {
-    runTest("regress_70703087.Test");
-  }
-
-  @Test
-  public void testRegress70736958() throws Exception {
-    runTest("regress_70736958.Test");
-  }
-
-  @Test
-  public void testRegress70737019() throws Exception {
-    runTest("regress_70737019.Test");
-  }
-
-  @Test
-  public void testRegress72361252() throws Exception {
-    runTest("regress_72361252.Test");
-  }
-
-  @Test
-  public void testMemberrebinding2() throws Exception {
-    runTest("memberrebinding2.Memberrebinding");
-  }
-
-  @Test
-  public void testMemberrebinding3() throws Exception {
-    runTest("memberrebinding3.Memberrebinding");
-  }
-
-  @Test
-  public void testMinification() throws Exception {
-    runTest("minification.Minification");
-  }
-
-  @Test
-  public void testEnclosingmethod() throws Exception {
-    runTest("enclosingmethod.Main");
-  }
-
-  @Test
-  public void testEnclosingmethodProguarded() throws Exception {
-    runTest("enclosingmethod_proguarded.Main");
-  }
-
-  @Test
-  public void testInterfaceInlining() throws Exception {
-    runTest("interfaceinlining.Main");
-  }
-
-  @Test
-  public void testSwitchmaps() throws Exception {
-    runTest("switchmaps.Switches");
-  }
-
-  private void runTest(String clazz) throws Exception {
-    runTest(
-        clazz, (expected, actual) -> assertEquals("Class " + clazz + " differs", expected, actual));
-  }
-
-  private void runTest(String clazz, BiConsumer<String, String> comparator) throws Exception {
-    assert comparator != null;
-    String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
-    String suffix = "_debuginfo_all";
-    Path inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION);
-    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
-    R8Command command =
-        R8Command.builder()
-            .addProgramFiles(inputJar)
-            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
-            .setMode(CompilationMode.DEBUG)
-            .setDisableTreeShaking(true)
-            .setDisableMinification(true)
-            .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
-            .setOutput(outputJar, OutputMode.ClassFile)
-            .build();
-    ToolHelper.runR8(command, options -> options.skipIR = true);
-    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(inputJar);
-    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
-    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
-    for (String descriptor : expected.getClassDescriptors()) {
-      byte[] expectedBytes = getClassAsBytes(expected, descriptor);
-      byte[] actualBytes = getClassAsBytes(actual, descriptor);
-      if (!Arrays.equals(expectedBytes, actualBytes)) {
-        String expectedString = replaceCatchThrowableByCatchAll(asmToString(expectedBytes));
-        String actualString = asmToString(actualBytes);
-        comparator.accept(expectedString, actualString);
-      }
-    }
-  }
-
-  private static String replaceCatchThrowableByCatchAll(String content) {
-    String catchThrowablePrefix = "methodVisitor.visitTryCatchBlock(";
-    String catchThrowableSuffix = ", \"java/lang/Throwable\");";
-    StringBuilder expected = new StringBuilder();
-    List<String> expectedLines = StringUtils.splitLines(content);
-    for (String line : expectedLines) {
-      if (line.startsWith(catchThrowablePrefix) && line.endsWith(catchThrowableSuffix)) {
-        expected.append(line.replace("\"java/lang/Throwable\"", "null"));
-      } else {
-        expected.append(line);
-      }
-      expected.append('\n');
-    }
-    return expected.toString();
-  }
-
-  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
-    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
-    Collections.sort(descriptorList);
-    return descriptorList;
-  }
-
-  public static byte[] getClassAsBytes(ClassFileResourceProvider inputJar, String descriptor)
-      throws Exception {
-    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
-  }
-
-  public static String asmToString(byte[] clazz) {
-    StringWriter stringWriter = new StringWriter();
-    printAsm(new PrintWriter(stringWriter), clazz);
-    return stringWriter.toString();
-  }
-
-  public static void printAsm(PrintWriter pw, byte[] clazz) {
-    new ClassReader(clazz).accept(new TraceClassVisitor(null, new ASMifierSorted(), pw), 0);
-  }
-
-  /** Sort methods and fields in the output of ASMifier to make diffing possible. */
-  private static class ASMifierSorted extends ASMifier {
-    private static class Part implements Comparable<Part> {
-
-      private final String key;
-      private final int start;
-      private final int end;
-
-      Part(String key, int start, int end) {
-        this.key = key;
-        this.start = start;
-        this.end = end;
-      }
-
-      @Override
-      public int compareTo(Part part) {
-        int i = key.compareTo(part.key);
-        return i != 0 ? i : Integer.compare(start, part.start);
-      }
-    }
-
-    private final List<Part> parts = new ArrayList<>();
-
-    ASMifierSorted() {
-      super(InternalOptions.ASM_VERSION, "cw", 0);
-    }
-
-    @Override
-    public ASMifier visitField(
-        int access, String name, String desc, String signature, Object value) {
-      init();
-      int i = text.size();
-      ASMifier res = super.visitField(access, name, desc, signature, value);
-      parts.add(new Part((String) text.get(i), i, text.size()));
-      return res;
-    }
-
-    @Override
-    public ASMifier visitMethod(
-        int access, String name, String desc, String signature, String[] exceptions) {
-      init();
-      int i = text.size();
-      ASMifier res = super.visitMethod(access, name, desc, signature, exceptions);
-      parts.add(new Part((String) text.get(i), i, text.size()));
-      return res;
-    }
-
-    private void init() {
-      if (parts.isEmpty()) {
-        parts.add(new Part("", 0, text.size()));
-      }
-    }
-
-    @Override
-    public void print(PrintWriter pw) {
-      init();
-      int end = parts.get(parts.size() - 1).end;
-      Collections.sort(parts);
-      parts.add(new Part("", end, text.size()));
-      ArrayList<Object> tmp = new ArrayList<>(text);
-      text.clear();
-      for (Part part : parts) {
-        for (int i = part.start; i < part.end; i++) {
-          text.add(tmp.get(i));
-        }
-      }
-      super.print(pw);
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 6f5f226..a5ec18b 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -192,9 +192,9 @@
     assertNotNull(parsedCommand.getR8Command());
     InternalOptions internalOptions = parsedCommand.getR8Command().getInternalOptions();
     assertNotNull(internalOptions);
-    assertTrue(internalOptions.proguardMapConsumer instanceof ProguardMapStringConsumer);
+    assertTrue(internalOptions.mapConsumer instanceof ProguardMapStringConsumer);
     ProguardMapStringConsumer mapStringConsumer =
-        (ProguardMapStringConsumer) internalOptions.proguardMapConsumer;
+        (ProguardMapStringConsumer) internalOptions.mapConsumer;
     FileConsumer proguardMapConsumer = (FileConsumer) mapStringConsumer.getStringConsumer();
     assertEquals(pgMap, proguardMapConsumer.getOutputPath());
   }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 1cfd1a1..c2a1ba2 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -26,9 +26,6 @@
   public static Collection<String[]> data() {
     String[] tests = {
         "arithmetic.Arithmetic",
-        "bridge.BridgeMethod",
-        "catchhandleroverlap.CatchHandlerOverlap",
-        "cse.CommonSubexpressionElimination",
         "constants.Constants",
         "controlflow.ControlFlow",
         "conversions.Conversions",
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 55e0435..b9c6c781 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -74,13 +74,12 @@
   private boolean allowUnusedProguardConfigurationRules = false;
   private boolean enableMissingLibraryApiModeling = true;
   private CollectingGraphConsumer graphConsumer = null;
-  private List<ExternalArtProfile> residualArtProfiles = new ArrayList<>();
-  private List<String> keepRules = new ArrayList<>();
-  private List<Path> mainDexRulesFiles = new ArrayList<>();
-  private List<String> applyMappingMaps = new ArrayList<>();
+  private final List<ExternalArtProfile> residualArtProfiles = new ArrayList<>();
+  private final List<String> keepRules = new ArrayList<>();
+  private final List<Path> mainDexRulesFiles = new ArrayList<>();
+  private final List<String> applyMappingMaps = new ArrayList<>();
   private final List<Path> features = new ArrayList<>();
-
-  private boolean createDefaultProguardMapConsumer = true;
+  private PartitionMapConsumer partitionMapConsumer = null;
 
   @Override
   public boolean isR8TestBuilder() {
@@ -110,20 +109,19 @@
       builder.setDisableMinification(true);
     }
     StringBuilder proguardMapBuilder = new StringBuilder();
-    if (createDefaultProguardMapConsumer) {
-      builder.setProguardMapConsumer(
-          new StringConsumer() {
-            @Override
-            public void accept(String string, DiagnosticsHandler handler) {
-              proguardMapBuilder.append(string);
-            }
+    builder.setProguardMapConsumer(
+        new StringConsumer() {
+          @Override
+          public void accept(String string, DiagnosticsHandler handler) {
+            proguardMapBuilder.append(string);
+          }
 
-            @Override
-            public void finished(DiagnosticsHandler handler) {
-              // Nothing to do.
-            }
-          });
-    }
+          @Override
+          public void finished(DiagnosticsHandler handler) {
+            // Nothing to do.
+          }
+        });
+    builder.setPartitionMapConsumer(partitionMapConsumer);
 
     if (!applyMappingMaps.isEmpty()) {
       try {
@@ -162,7 +160,7 @@
             app.get(),
             box.proguardConfiguration,
             box.syntheticProguardRules,
-            createDefaultProguardMapConsumer ? proguardMapBuilder.toString() : null,
+            proguardMapBuilder.toString(),
             graphConsumer,
             getMinApiLevel(),
             features,
@@ -803,11 +801,6 @@
     return self();
   }
 
-  public T noDefaultProguardMapConsumer() {
-    createDefaultProguardMapConsumer = false;
-    return self();
-  }
-
   public T addArtProfileForRewriting(ArtProfileProvider artProfileProvider) {
     return addArtProfileForRewriting(
         artProfileProvider,
@@ -838,4 +831,9 @@
     getBuilder().setFakeCompilerVersion(version);
     return self();
   }
+
+  public T setPartitionMapConsumer(PartitionMapConsumer partitionMapConsumer) {
+    this.partitionMapConsumer = partitionMapConsumer;
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/code/PassThroughTest.java b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
deleted file mode 100644
index acbb529..0000000
--- a/src/test/java/com/android/tools/r8/code/PassThroughTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.code;
-
-import static junit.framework.Assert.assertSame;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.CfFrontendExamplesTest;
-import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.DirectoryClassFileProvider;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-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 PassThroughTest extends TestBase {
-
-  private final String EXPECTED = StringUtils.lines("0", "foo", "0", "foo", "foo");
-
-  @Parameter(0)
-  public TestParameters parameters;
-
-  @Parameter(1)
-  public boolean keepDebug;
-
-  @Parameters(name = "{0}, keep-debug: {1}")
-  public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withCfRuntimes().build(), BooleanUtils.values());
-  }
-
-  @Test
-  public void testJvm() throws Exception {
-    assumeTrue(keepDebug);
-    testForJvm(parameters)
-        .addProgramClasses(Main.class)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED);
-
-    // Check that reading the same input is actual matches.
-    ClassFileResourceProvider original =
-        DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
-    verifyInstructionsForMethodMatchingExpectation(original, "main", true, true);
-    verifyInstructionsForMethodMatchingExpectation(original, "exceptionTest", true, true);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    Path outputJar = temp.newFile("output.jar").toPath();
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class)
-        .addKeepAllClassesRule()
-        .enableInliningAnnotations()
-        .applyIf(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
-        .compile()
-        .writeToZip(outputJar)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED);
-    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
-    verifyInstructionsForMethodMatchingExpectation(actual, "main", keepDebug, false);
-    verifyInstructionsForMethodMatchingExpectation(actual, "exceptionTest", keepDebug, false);
-  }
-
-  @Test
-  public void testR8ByteCodePassThrough() throws Exception {
-    Path outputJar = temp.newFile("output.jar").toPath();
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class)
-        .addKeepAllClassesRule()
-        .enableInliningAnnotations()
-        .applyIf(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
-        .addOptionsModification(
-            internalOptions -> {
-              internalOptions.testing.cfByteCodePassThrough =
-                  method -> !method.name.toString().equals("<init>");
-            })
-        .compile()
-        .writeToZip(outputJar)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED);
-    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
-    verifyInstructionsForMethodMatchingExpectation(actual, "main", keepDebug, true);
-    verifyInstructionsForMethodMatchingExpectation(actual, "exceptionTest", keepDebug, true);
-  }
-
-  private void verifyInstructionsForMethodMatchingExpectation(
-      ClassFileResourceProvider actual, String methodName, boolean checkDebug, boolean expectation)
-      throws Exception {
-    ClassFileResourceProvider original =
-        DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
-    String descriptor = DescriptorUtils.javaTypeToDescriptor(Main.class.getTypeName());
-    byte[] expectedBytes = CfFrontendExamplesTest.getClassAsBytes(original, descriptor);
-    byte[] actualBytes = CfFrontendExamplesTest.getClassAsBytes(actual, descriptor);
-    if (!Arrays.equals(expectedBytes, actualBytes)) {
-      String expectedString = CfFrontendExamplesTest.asmToString(expectedBytes);
-      String actualString = CfFrontendExamplesTest.asmToString(actualBytes);
-      verifyInstructionsForMethodMatchingExpectation(
-          getMethodInstructions(expectedString, methodName),
-          getMethodInstructions(actualString, methodName),
-          checkDebug,
-          expectation);
-    }
-  }
-
-  private String getMethodInstructions(String asm, String methodName) {
-    int methodIndexStart =
-        asm.indexOf(
-            "methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, \""
-                + methodName
-                + "\",");
-    methodIndexStart = asm.indexOf("methodVisitor.visitCode();", methodIndexStart);
-    int methodIndexEnd = asm.indexOf("methodVisitor.visitEnd();", methodIndexStart);
-    return asm.substring(methodIndexStart, methodIndexEnd);
-  }
-
-  private void verifyInstructionsForMethodMatchingExpectation(
-      String originalInstructions,
-      String actualInstructions,
-      boolean checkDebug,
-      boolean expectation) {
-    if (checkDebug) {
-      // We may rewrite jump instructions, so filter those out.
-      originalInstructions = filter(originalInstructions, this::isNotLabelOrJumpInstruction);
-      actualInstructions = filter(actualInstructions, this::isNotLabelOrJumpInstruction);
-    } else {
-      originalInstructions = filter(originalInstructions, this::isNotDebugInstructionOrJump);
-      actualInstructions = filter(actualInstructions, this::isNotLabelOrJumpInstruction);
-    }
-    assertSame(expectation, actualInstructions.equals(originalInstructions));
-  }
-
-  private String filter(String instructions, Predicate<String> predicate) {
-    return StringUtils.splitLines(instructions).stream()
-        .filter(predicate)
-        .map(instr -> instr + "\n")
-        .collect(Collectors.joining());
-  }
-
-  private boolean isNotDebugInstructionOrJump(String instruction) {
-    return !(instruction.startsWith("methodVisitor.visitLocalVariable")
-        || instruction.startsWith("methodVisitor.visitLabel")
-        || instruction.startsWith("Label")
-        || instruction.startsWith("methodVisitor.visitLineNumber")
-        || instruction.startsWith("methodVisitor.visitJumpInsn"));
-  }
-
-  private boolean isNotLabelOrJumpInstruction(String instruction) {
-    return !(instruction.startsWith("Label")
-        || instruction.startsWith("methodVisitor.visitJumpInsn")
-        || instruction.startsWith("methodVisitor.visitLabel"));
-  }
-
-  public static class Main {
-
-    public static void main(String[] args) {
-      int i = 0;
-      System.out.println(i);
-      int j = 0;
-      String foo = "foo";
-      // Keep the false to have R8 remove it.
-      if (false) {
-        System.out.println(foo);
-      }
-      System.out.println(foo);
-      System.out.println(j);
-      System.out.println(phiTest(args.length > 0 ? args[0] : null));
-      System.out.println(exceptionTest(args.length > 0 ? args[0] : null));
-    }
-
-    @NeverInline
-    public static String phiTest(String arg) {
-      String result;
-      if (arg == null) {
-        result = "foo";
-      } else {
-        result = "bar";
-      }
-      return result;
-    }
-
-    @NeverInline
-    public static String exceptionTest(String arg) {
-      try {
-        return arg.toLowerCase();
-      } catch (NullPointerException ignored) {
-        return "foo";
-      }
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 76f9758..563fc09 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -60,16 +60,6 @@
   }
 
   @Test
-  public void testBridgeMethod() throws Exception {
-    testDebugging("bridge", "BridgeMethod");
-  }
-
-  @Test
-  public void testCommonSubexpressionElimination() throws Exception {
-    testDebugging("cse", "CommonSubexpressionElimination");
-  }
-
-  @Test
   public void testConstants() throws Exception {
     testDebugging("constants", "Constants");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
index eebc725..1872678 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PartialDesugaringTest.java
@@ -129,9 +129,12 @@
 
   @Test
   public void test() throws Exception {
+    InternalOptions options = new InternalOptions();
+    options
+        .getArtProfileOptions()
+        .setAllowReadingEmptyArtProfileProvidersMultipleTimesForTesting(true);
     SupportedClasses supportedClasses =
-        new SupportedClassesGenerator(
-                new InternalOptions(), ToolHelper.getAndroidJar(AndroidApiLevel.U))
+        new SupportedClassesGenerator(options, ToolHelper.getAndroidJar(AndroidApiLevel.U))
             .run(librarySpecification.getDesugarJdkLibs(), librarySpecification.getSpecification());
 
     for (AndroidApiLevel api : getRelevantApiLevels()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeClasspathTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesClasspathTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeClasspathTest.java
rename to src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesClasspathTest.java
index 7661218..663c241 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeClasspathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesClasspathTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.sealed;
 
-
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
@@ -17,7 +16,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class SealedAttributeClasspathTest extends TestBase {
+public class SealedClassesClasspathTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
similarity index 80%
copy from src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
copy to src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
index 2fc66f1..16009e6 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
@@ -16,7 +16,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.examples.jdk17.Sealed;
+import com.android.tools.r8.examples.jdk17.EnumSealed;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -25,12 +25,12 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class SealedAttributeTest extends TestBase {
+public class SealedClassesEnumJdk17CompiledTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
 
-  static final String EXPECTED = StringUtils.lines("R8 compiler", "D8 compiler");
+  static final String EXPECTED = StringUtils.lines("A", "a B");
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -42,16 +42,16 @@
     parameters.assumeJvmTestParameters();
     assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17));
     testForJvm(parameters)
-        .addRunClasspathFiles(Sealed.jar())
-        .run(parameters.getRuntime(), Sealed.Main.typeName())
+        .addRunClasspathFiles(EnumSealed.jar())
+        .run(parameters.getRuntime(), EnumSealed.Main.typeName())
         .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testDesugaring() throws Exception {
     testForDesugaring(parameters)
-        .addProgramFiles(Sealed.jar())
-        .run(parameters.getRuntime(), Sealed.Main.typeName())
+        .addProgramFiles(EnumSealed.jar())
+        .run(parameters.getRuntime(), EnumSealed.Main.typeName())
         .applyIf(
             c ->
                 DesugarTestConfiguration.isNotJavac(c)
@@ -65,9 +65,9 @@
     parameters.assumeR8TestParameters();
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
-            .addProgramFiles(Sealed.jar())
+            .addProgramFiles(EnumSealed.jar())
             .setMinApi(parameters)
-            .addKeepMainRule(Sealed.Main.typeName());
+            .addKeepMainRule(EnumSealed.Main.typeName());
     if (parameters.isCfRuntime()) {
       assertThrows(
           CompilationFailedException.class,
@@ -80,7 +80,7 @@
                                   "Sealed classes are not supported as program classes")))));
     } else {
       builder
-          .run(parameters.getRuntime(), Sealed.Main.typeName())
+          .run(parameters.getRuntime(), EnumSealed.Main.typeName())
           .assertSuccessWithOutput(EXPECTED);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
new file mode 100644
index 0000000..330da7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.sealed;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.StringUtils;
+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 SealedClassesIllegalSubclassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  static final String EXPECTED = StringUtils.lines("Success!");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+    builder
+        .addProgramClasses(TestClass.class, Sub1.class, Sub2.class, Sub3.class)
+        .addProgramClassFileData(getTransformedClasses());
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17));
+    testForJvm(parameters)
+        .apply(this::addTestClasses)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatMatches(containsString("cannot inherit from sealed class"));
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    testForDesugaring(parameters)
+        .apply(this::addTestClasses)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            DesugarTestConfiguration::isNotJavac,
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            c -> parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK17),
+            r ->
+                r.assertFailureWithErrorThatMatches(
+                    containsString("cannot inherit from sealed class")),
+            r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .apply(this::addTestClasses)
+            .setMinApi(parameters)
+            .addKeepMainRule(TestClass.class);
+    if (parameters.isCfRuntime()) {
+      // TODO(b/227160052): Support sealed classes for R8 class file output.
+      assertThrows(
+          CompilationFailedException.class,
+          () ->
+              builder.compileWithExpectedDiagnostics(
+                  diagnostics ->
+                      diagnostics.assertErrorThatMatches(
+                          diagnosticMessage(
+                              containsString(
+                                  "Sealed classes are not supported as program classes")))));
+    } else {
+      builder
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Success!");
+    }
+  }
+
+  public byte[] getTransformedClasses() throws Exception {
+    return transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Sub1();
+      new Sub2();
+      new Sub3();
+      System.out.println("Success!");
+    }
+  }
+
+  abstract static class C /* permits Sub1, Sub2 */ {}
+
+  static class Sub1 extends C {}
+
+  static class Sub2 extends C {}
+
+  static class Sub3 extends C {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
rename to src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
index 2fc66f1..2a6eee8 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
@@ -25,7 +25,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class SealedAttributeTest extends TestBase {
+public class SealedClassesJdk17CompiledTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesLibraryTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeLibraryTest.java
rename to src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesLibraryTest.java
index e8217c9..e94bcec 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesLibraryTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.sealed;
 
-
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
@@ -17,7 +16,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class SealedAttributeLibraryTest extends TestBase {
+public class SealedClassesLibraryTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
similarity index 66%
copy from src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
copy to src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
index 2fc66f1..420c077 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
@@ -13,10 +13,10 @@
 import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.examples.jdk17.Sealed;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -25,33 +25,39 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class SealedAttributeTest extends TestBase {
+public class SealedClassesTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
 
-  static final String EXPECTED = StringUtils.lines("R8 compiler", "D8 compiler");
+  static final String EXPECTED = StringUtils.lines("Success!");
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
+  private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+    builder
+        .addProgramClasses(TestClass.class, Sub1.class, Sub2.class)
+        .addProgramClassFileData(getTransformedClasses());
+  }
+
   @Test
   public void testJvm() throws Exception {
     parameters.assumeJvmTestParameters();
     assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17));
     testForJvm(parameters)
-        .addRunClasspathFiles(Sealed.jar())
-        .run(parameters.getRuntime(), Sealed.Main.typeName())
+        .apply(this::addTestClasses)
+        .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testDesugaring() throws Exception {
     testForDesugaring(parameters)
-        .addProgramFiles(Sealed.jar())
-        .run(parameters.getRuntime(), Sealed.Main.typeName())
+        .apply(this::addTestClasses)
+        .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
             c ->
                 DesugarTestConfiguration.isNotJavac(c)
@@ -65,10 +71,11 @@
     parameters.assumeR8TestParameters();
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
-            .addProgramFiles(Sealed.jar())
+            .apply(this::addTestClasses)
             .setMinApi(parameters)
-            .addKeepMainRule(Sealed.Main.typeName());
+            .addKeepMainRule(TestClass.class);
     if (parameters.isCfRuntime()) {
+      // TODO(b/227160052): Support sealed classes for R8 class file output.
       assertThrows(
           CompilationFailedException.class,
           () ->
@@ -79,9 +86,26 @@
                               containsString(
                                   "Sealed classes are not supported as program classes")))));
     } else {
-      builder
-          .run(parameters.getRuntime(), Sealed.Main.typeName())
-          .assertSuccessWithOutput(EXPECTED);
+      builder.run(parameters.getRuntime(), TestClass.class).assertSuccessWithOutput(EXPECTED);
     }
   }
+
+  public byte[] getTransformedClasses() throws Exception {
+    return transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Sub1();
+      new Sub2();
+      System.out.println("Success!");
+    }
+  }
+
+  abstract static class C /* permits Sub1, Sub2 */ {}
+
+  static class Sub1 extends C {}
+
+  static class Sub2 extends C {}
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
index d9e84f4..5d47744 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
@@ -45,7 +45,6 @@
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .setMinApi(parameters)
-        .compile()
         .run(parameters.getRuntime(), classToTest)
         .assertSuccess()
         .inspectStdOut(this::assertLines2By2Correct);
@@ -83,119 +82,143 @@
 
     @NeverInline
     private static void constructorTest() {
-      new TestClass(42);
-      System.out.println("42");
+      constructorOutline();
       new TestClass(MyEnum1.A);
-      System.out.println("0");
+      System.out.println("1");
       new TestClass(MyEnum1.B);
-      System.out.println("1");
+      System.out.println("2");
       new TestClass(MyEnum2.A);
-      System.out.println("0");
+      System.out.println("2");
       new TestClass(MyEnum2.B);
-      System.out.println("1");
+      System.out.println("3");
       new TestClass(MyEnum3.A);
-      System.out.println("0");
+      System.out.println("3");
       new TestClass(MyEnum3.B);
-      System.out.println("1");
+      System.out.println("4");
+    }
+
+    @NeverInline
+    private static void constructorOutline() {
+      // This method is outlined so it is not reprocessed in the second round of IR processing.
+      new TestClass(38);
+      System.out.println("42");
+      new TestClass(40);
+      System.out.println("44");
     }
 
     @NeverInline
     private static void staticTest() {
-      staticMethod(42);
-      System.out.println("42");
+      staticOutline();
       staticMethod(MyEnum1.A);
-      System.out.println("0");
+      System.out.println("1");
       staticMethod(MyEnum1.B);
-      System.out.println("1");
+      System.out.println("2");
       staticMethod(MyEnum2.A);
-      System.out.println("0");
+      System.out.println("2");
       staticMethod(MyEnum2.B);
-      System.out.println("1");
+      System.out.println("3");
       staticMethod(MyEnum3.A);
-      System.out.println("0");
+      System.out.println("3");
       staticMethod(MyEnum3.B);
-      System.out.println("1");
+      System.out.println("4");
+    }
+
+    @NeverInline
+    private static void staticOutline() {
+      // This method is outlined so it is not reprocessed in the second round of IR processing.
+      staticMethod(38);
+      System.out.println("42");
+      staticMethod(40);
+      System.out.println("44");
     }
 
     @NeverInline
     private static void virtualTest() {
       TestClass testClass = new TestClass();
-      testClass.virtualMethod(42);
-      System.out.println("42");
+      virtualOutline(testClass);
       testClass.virtualMethod(MyEnum1.A);
-      System.out.println("0");
+      System.out.println("1");
       testClass.virtualMethod(MyEnum1.B);
-      System.out.println("1");
+      System.out.println("2");
       testClass.virtualMethod(MyEnum2.A);
-      System.out.println("0");
+      System.out.println("2");
       testClass.virtualMethod(MyEnum2.B);
-      System.out.println("1");
+      System.out.println("3");
       testClass.virtualMethod(MyEnum3.A);
-      System.out.println("0");
+      System.out.println("3");
       testClass.virtualMethod(MyEnum3.B);
-      System.out.println("1");
+      System.out.println("4");
+    }
+
+    @NeverInline
+    private static void virtualOutline(TestClass testClass) {
+      // This method is outlined so it is not reprocessed in the second round of IR processing.
+      testClass.virtualMethod(38);
+      System.out.println("42");
+      testClass.virtualMethod(40);
+      System.out.println("44");
     }
 
     public TestClass() {}
 
     @NeverInline
     public TestClass(MyEnum1 e1) {
-      System.out.println(e1.ordinal());
+      System.out.println(e1.ordinal() + 1);
     }
 
     @NeverInline
     public TestClass(MyEnum2 e2) {
-      System.out.println(e2.ordinal());
+      System.out.println(e2.ordinal() + 2);
     }
 
     @NeverInline
     public TestClass(MyEnum3 e3) {
-      System.out.println(e3.ordinal());
+      System.out.println(e3.ordinal() + 3);
     }
 
     @NeverInline
     public TestClass(int i) {
-      System.out.println(i);
+      System.out.println(i + 4);
     }
 
     @NeverInline
     public void virtualMethod(MyEnum1 e1) {
-      System.out.println(e1.ordinal());
+      System.out.println(e1.ordinal() + 1);
     }
 
     @NeverInline
     public void virtualMethod(MyEnum2 e2) {
-      System.out.println(e2.ordinal());
+      System.out.println(e2.ordinal() + 2);
     }
 
     @NeverInline
     public void virtualMethod(MyEnum3 e3) {
-      System.out.println(e3.ordinal());
+      System.out.println(e3.ordinal() + 3);
     }
 
     @NeverInline
     public void virtualMethod(int i) {
-      System.out.println(i);
+      System.out.println(i + 4);
     }
 
     @NeverInline
     public static void staticMethod(MyEnum1 e1) {
-      System.out.println(e1.ordinal());
+      System.out.println(e1.ordinal() + 1);
     }
 
     @NeverInline
     public static void staticMethod(MyEnum2 e2) {
-      System.out.println(e2.ordinal());
+      System.out.println(e2.ordinal() + 2);
     }
 
     @NeverInline
     public static void staticMethod(MyEnum3 e3) {
-      System.out.println(e3.ordinal());
+      System.out.println(e3.ordinal() + 3);
     }
 
     @NeverInline
     public static void staticMethod(int i) {
-      System.out.println(i);
+      System.out.println(i + 4);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
index d14fe97..1719dd9 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
@@ -1,16 +1,16 @@
 // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-
 package com.android.tools.r8.enumunboxing;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
@@ -52,6 +52,7 @@
         .enableNeverClassInliningAnnotations()
         .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .addOptionsModification(options -> enableEnumOptions(options, enumValueOptimization))
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
         .setMinApi(parameters)
@@ -59,45 +60,97 @@
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines(
-            "A.m(A : MyEnum, 42 : int)", "B.m(A : MyEnum, 42 : int)", "B.m(42 : int, A : MyEnum)");
+            "Middle.m(A : MyEnum, 42 : int)",
+            "Bottom.m(A : MyEnum, 42 : int)",
+            "Bottom.m(42 : int, A : MyEnum)",
+            "Middle.m(A : MyEnum, 42 : int)",
+            "Middle2.m(A : MyEnum, 42 : int)",
+            "Middle.m(A : MyEnum, 42 : int)",
+            "Bottom.m(A : MyEnum, 42 : int)",
+            "Middle.m(A : MyEnum, 42 : int)",
+            "Middle2.m(A : MyEnum, 42 : int)",
+            "Something");
   }
 
   private void inspect(CodeInspector inspector) {
-    MethodSubject methodOnA = inspector.clazz(A.class).virtualMethods().get(0);
-    MethodSubject methodOnB =
-        inspector.clazz(B.class).uniqueMethodWithFinalName(methodOnA.getFinalName());
-    assertThat(methodOnB, isPresent());
-    // TODO(b/171784168): Should be true.
-    assertFalse(methodOnB.streamInstructions().anyMatch(x -> x.asDexInstruction().isInvokeSuper()));
+    MethodSubject methodOnMiddle = inspector.clazz(Middle.class).virtualMethods().get(0);
+    MethodSubject methodOnBottom =
+        inspector.clazz(Bottom.class).uniqueMethodWithFinalName(methodOnMiddle.getFinalName());
+    assertThat(methodOnBottom, isPresent());
+    assertTrue(
+        methodOnBottom
+            .streamInstructions()
+            .anyMatch(
+                i ->
+                    i.asDexInstruction().isInvokeSuper()
+                        && i.getMethod() == methodOnMiddle.getMethod().getReference()));
   }
 
   static class TestClass {
 
     public static void main(String[] args) {
       MyEnum value = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
-      new B().m(value, 42);
-      new B().m(42, value);
+      new Bottom().m(value, 42);
+      new Bottom().m(42, value);
+      new Middle().m(value, 42);
+      new Middle2().m(value, 42);
+      fromTop(new Bottom(), value);
+      fromTop(new Middle(), value);
+      fromTop(new Middle2(), value);
+      new Middle().checkNotNullOrPrintSomething(value);
+      new Bottom().checkNotNullOrPrintSomething(value);
+    }
+
+    @NeverInline
+    public static void fromTop(Top top, MyEnum value) {
+      top.m(value, 42);
     }
   }
 
   @NeverClassInline
   @NoVerticalClassMerging
-  static class A {
+  abstract static class Top {
 
     @NeverInline
+    abstract void m(MyEnum x, int y);
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  static class Middle2 extends Top {
+    @NeverInline
     void m(MyEnum x, int y) {
-      System.out.println("A.m(" + x.toString() + " : MyEnum, " + y + " : int)");
+      System.out.println("Middle2.m(" + x.toString() + " : MyEnum, " + y + " : int)");
     }
   }
 
   @NeverClassInline
-  static class B extends A {
+  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  static class Middle extends Top {
+
+    @NeverInline
+    void m(MyEnum x, int y) {
+      System.out.println("Middle.m(" + x.toString() + " : MyEnum, " + y + " : int)");
+    }
+
+    @NeverInline
+    void checkNotNullOrPrintSomething(MyEnum e) {
+      if (e == null) {
+        throw new NullPointerException();
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class Bottom extends Middle {
 
     @KeepConstantArguments
     @NeverInline
     @NoMethodStaticizing
     void m(int x, MyEnum y) {
-      System.out.println("B.m(" + x + " : int, " + y.toString() + " : MyEnum)");
+      System.out.println("Bottom.m(" + x + " : int, " + y.toString() + " : MyEnum)");
     }
 
     @KeepConstantArguments
@@ -105,7 +158,14 @@
     @Override
     void m(MyEnum x, int y) {
       super.m(x, y);
-      System.out.println("B.m(" + x.toString() + " : MyEnum, " + y + " : int)");
+      System.out.println("Bottom.m(" + x.toString() + " : MyEnum, " + y + " : int)");
+    }
+
+    @KeepConstantArguments
+    @NeverInline
+    @Override
+    void checkNotNullOrPrintSomething(MyEnum e) {
+      System.out.println("Something");
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractEnumMergingTest.java
new file mode 100644
index 0000000..9335996
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractEnumMergingTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.enumunboxing.enummerging;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AbstractEnumMergingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public AbstractEnumMergingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("336", "74", "96", "44");
+  }
+
+  enum MyEnum {
+    A(8) {
+      @NeverInline
+      @Override
+      public long operate(long another) {
+        return num * another;
+      }
+    },
+    B(32) {
+      @NeverInline
+      @Override
+      public long operate(long another) {
+        return num + another;
+      }
+    };
+    final long num;
+
+    MyEnum(long num) {
+      this.num = num;
+    }
+
+    public abstract long operate(long another);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.operate(42));
+      System.out.println(MyEnum.B.operate(42));
+      System.out.println(indirect(MyEnum.A));
+      System.out.println(indirect(MyEnum.B));
+    }
+
+    @NeverInline
+    public static long indirect(MyEnum e) {
+      return e.operate(12);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/BasicEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/BasicEnumMergingTest.java
index 56f3d9e..0eb13fb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/BasicEnumMergingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/BasicEnumMergingTest.java
@@ -48,9 +48,7 @@
                     .assertNotUnboxed(EnumWithVirtualOverrideAndPrivateField.class))
         .enableInliningAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .addOptionsModification(opt -> opt.testing.enableEnumUnboxingDebugLogs = true)
         .setMinApi(parameters)
-        .allowDiagnosticInfoMessages()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("a", "B", "a", "B", "a", "B", "A 1 1.0 A", "B");
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/DeterministicEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/DeterministicEnumMergingTest.java
new file mode 100644
index 0000000..00bb1cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/DeterministicEnumMergingTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.enumunboxing.enummerging;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DeterministicEnumMergingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public DeterministicEnumMergingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxingDeterminism() throws Exception {
+    R8TestCompileResult compile1 =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addKeepMainRule(Main.class)
+            .addKeepRules(enumKeepRules.getKeepRules())
+            .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
+            .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+            .enableInliningAnnotations()
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .setMinApi(parameters)
+            .compile();
+    R8TestCompileResult compile2 =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addKeepMainRule(Main.class)
+            .addKeepRules(enumKeepRules.getKeepRules())
+            .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
+            .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+            .enableInliningAnnotations()
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .setMinApi(parameters)
+            .compile();
+    assertIdenticalInspectors(compile1.inspector(), compile2.inspector());
+    compile1
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("336", "74", "22", "10794", "96", "44", "52", "3084");
+  }
+
+  enum MyEnum {
+    // The more cases, the highest the chance to trigger non determinism.
+    // The operate methods have to be moved in the same order, and the dispatch method has to
+    // be generated in the same order too.
+    A(8) {
+      @NeverInline
+      @Override
+      public long operate(long another) {
+        return num * another;
+      }
+    },
+    B(32) {
+      @NeverInline
+      @Override
+      public long operate(long another) {
+        return num + another;
+      }
+    },
+    C(64) {
+      @NeverInline
+      @Override
+      public long operate(long another) {
+        return num - another;
+      }
+    },
+    D(256) {
+
+      @NeverInline
+      @Override
+      public long operate(long another) {
+        return num * another + another;
+      }
+    };
+    final long num;
+
+    MyEnum(long num) {
+      this.num = num;
+    }
+
+    public abstract long operate(long another);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.operate(42));
+      System.out.println(MyEnum.B.operate(42));
+      System.out.println(MyEnum.C.operate(42));
+      System.out.println(MyEnum.D.operate(42));
+      System.out.println(indirect(MyEnum.A));
+      System.out.println(indirect(MyEnum.B));
+      System.out.println(indirect(MyEnum.C));
+      System.out.println(indirect(MyEnum.D));
+    }
+
+    @NeverInline
+    public static long indirect(MyEnum e) {
+      return e.operate(12);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/examples/ExamplesTestBase.java b/src/test/java/com/android/tools/r8/examples/ExamplesTestBase.java
index 093702c..cd9e112 100644
--- a/src/test/java/com/android/tools/r8/examples/ExamplesTestBase.java
+++ b/src/test/java/com/android/tools/r8/examples/ExamplesTestBase.java
@@ -45,6 +45,7 @@
   public void runTestR8() throws Exception {
     parameters.assumeR8TestParameters();
     testForR8(parameters.getBackend())
+        .addOptionsModification(o -> o.testing.roundtripThroughLir = true)
         .setMinApi(parameters)
         .addProgramClasses(getTestClasses())
         .addKeepMainRule(getMainClass())
@@ -52,7 +53,6 @@
         .assertSuccessWithOutput(getExpected());
   }
 
-  @Test
   public void runTestDebugComparator() throws Exception {
     Assume.assumeFalse(ToolHelper.isWindows());
     Assume.assumeFalse(
diff --git a/src/test/examples/bridge/BridgeMethod.java b/src/test/java/com/android/tools/r8/examples/bridge/BridgeMethod.java
similarity index 70%
rename from src/test/examples/bridge/BridgeMethod.java
rename to src/test/java/com/android/tools/r8/examples/bridge/BridgeMethod.java
index 9e82a29..685f885 100644
--- a/src/test/examples/bridge/BridgeMethod.java
+++ b/src/test/java/com/android/tools/r8/examples/bridge/BridgeMethod.java
@@ -1,10 +1,11 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package bridge;
+package com.android.tools.r8.examples.bridge;
 
 abstract class Super<T> {
   public abstract int method(T t0, T t1);
+
   public abstract int rangeMethod(T t0, T t1, T t2, T t3, T t4, T t5);
 }
 
@@ -27,10 +28,12 @@
   }
 
   public static void main(String[] args) {
+    int i = 0;
     Super<Integer> instance = new BridgeMethod();
-    instance.method(1, 2);
-    instance.method(2, 1);
-    instance.rangeMethod(1, 2, 3, 4, 5, 6);
-    instance.rangeMethod(2, 1, 3, 4, 5, 6);
+    i += instance.method(1, 2);
+    i += instance.method(2, 1);
+    i += instance.rangeMethod(1, 2, 3, 4, 5, 6);
+    i += instance.rangeMethod(2, 1, 3, 4, 5, 6);
+    System.out.println(i);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/examples/bridge/BridgeMethodTestRunner.java b/src/test/java/com/android/tools/r8/examples/bridge/BridgeMethodTestRunner.java
new file mode 100644
index 0000000..47e9945
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/bridge/BridgeMethodTestRunner.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.examples.bridge;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BridgeMethodTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public BridgeMethodTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return BridgeMethod.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() {
+    return ImmutableList.of(BridgeMethod.class, Super.class);
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("26");
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/examples/catchhandleroverlap/CatchHandlerOverlap.java b/src/test/java/com/android/tools/r8/examples/catchhandleroverlap/CatchHandlerOverlap.java
new file mode 100644
index 0000000..c66cd26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/catchhandleroverlap/CatchHandlerOverlap.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.examples.catchhandleroverlap;
+
+public class CatchHandlerOverlap {
+
+  private static void f() throws Exception {
+    throw new Exception("f");
+  }
+
+  private static void g() throws Exception {
+    throw new Exception("g");
+  }
+
+  private static void h(
+      int i1,
+      int i2,
+      int i3,
+      int i4,
+      int i5,
+      int i6,
+      int i7,
+      int i8,
+      int i9,
+      int i10,
+      int i11,
+      int i12,
+      int i13,
+      int i14,
+      int i15,
+      int i16,
+      int i17) {
+    System.out.println(
+        i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 + i12 + i13 + i14 + i15 + i16 + i17);
+    try {
+      f();
+    } catch (Exception e0) {
+      try {
+        g();
+      } catch (Exception e1) {
+        System.out.println(e0.getMessage() + " " + e1.getMessage());
+      }
+    }
+  }
+
+  public static void main(String[] args) {
+    h(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/examples/catchhandleroverlap/CatchHandlerOverlapTestRunner.java b/src/test/java/com/android/tools/r8/examples/catchhandleroverlap/CatchHandlerOverlapTestRunner.java
new file mode 100644
index 0000000..f8d0e85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/catchhandleroverlap/CatchHandlerOverlapTestRunner.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.examples.catchhandleroverlap;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CatchHandlerOverlapTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public CatchHandlerOverlapTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return CatchHandlerOverlap.class;
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("153", "f g");
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+}
diff --git a/src/test/examples/cse/CommonSubexpressionElimination.java b/src/test/java/com/android/tools/r8/examples/cse/CommonSubexpressionElimination.java
similarity index 95%
rename from src/test/examples/cse/CommonSubexpressionElimination.java
rename to src/test/java/com/android/tools/r8/examples/cse/CommonSubexpressionElimination.java
index 3c64a1d..4ae0ba7 100644
--- a/src/test/examples/cse/CommonSubexpressionElimination.java
+++ b/src/test/java/com/android/tools/r8/examples/cse/CommonSubexpressionElimination.java
@@ -1,11 +1,11 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 // This code is not run directly. It needs to be compiled to dex code.
 // 'arithmetic.dex' is what is run.
 
-package cse;
+package com.android.tools.r8.examples.cse;
 
 public class CommonSubexpressionElimination {
 
@@ -74,7 +74,6 @@
     }
   }
 
-
   public static void main(String[] args) {
     System.out.println(divNoCatch(1, 0, 1));
     System.out.println(divNoCatch2(1, 0, 2));
diff --git a/src/test/java/com/android/tools/r8/examples/cse/CommonSubexpressionEliminationTestRunner.java b/src/test/java/com/android/tools/r8/examples/cse/CommonSubexpressionEliminationTestRunner.java
new file mode 100644
index 0000000..1f50713
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/cse/CommonSubexpressionEliminationTestRunner.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.examples.cse;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CommonSubexpressionEliminationTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public CommonSubexpressionEliminationTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return CommonSubexpressionElimination.class;
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("1", "1", "2 2", "2", "3", "3", "4 4", "4", "A", "B");
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/examples/jdk17/EnumSealed.java b/src/test/java/com/android/tools/r8/examples/jdk17/EnumSealed.java
new file mode 100644
index 0000000..c260155
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/jdk17/EnumSealed.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.examples.jdk17;
+
+import com.android.tools.r8.examples.JavaExampleClassProxy;
+import java.nio.file.Path;
+
+public class EnumSealed {
+
+  private static final String EXAMPLE_FILE = "examplesJava17/enum_sealed";
+
+  public static final JavaExampleClassProxy Enum =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "enum_sealed/Enum");
+  public static final JavaExampleClassProxy EnumB =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "enum_sealed/Enum$1");
+  public static final JavaExampleClassProxy Main =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "enum_sealed/Main");
+
+  public static Path jar() {
+    return JavaExampleClassProxy.examplesJar(EXAMPLE_FILE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index a376a2e..66adde9 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -380,7 +380,7 @@
       newReturnBlock.iterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
       Instruction constInstruction = new ConstNumber(newConstValue, 10);
       Instruction addInstruction =
-          new Add(NumericType.INT, newReturnValue, oldReturnValue, newConstValue);
+          Add.create(NumericType.INT, newReturnValue, oldReturnValue, newConstValue);
       iterator.previous();
       iterator.add(constInstruction);
       iterator.add(addInstruction);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationMonitorTest.java
index 5c769f0..a1c1662 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationMonitorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationMonitorTest.java
@@ -42,10 +42,7 @@
         .enableInliningAnnotations()
         .addKeepAttributeLineNumberTable()
         .addKeepAttributeSourceFile()
-        .addOptionsModification(
-            options -> {
-              options.proguardMapConsumer = null;
-            })
+        .addOptionsModification(options -> options.mapConsumer = null)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello World!")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 5db89dc..983dfdb 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -110,9 +110,9 @@
     Value value2 = new Value(2, TypeElement.getInt(), null);
     ConstNumber const2 = new ConstNumber(value2, 2);
     Value value3 = new Value(2, TypeElement.getInt(), null);
-    Add add0 = new Add(NumericType.INT, value3, value0, value1);
+    Add add0 = Add.create(NumericType.INT, value3, value0, value1);
     add0.setPosition(Position.none());
-    Add add1 = new Add(NumericType.INT, value3, value0, value2);
+    Add add1 = Add.create(NumericType.INT, value3, value0, value2);
     add1.setPosition(Position.none());
     value0.computeNeedsRegister();
     assertTrue(value0.needsRegister());
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index 44a9963..8cfa77f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -122,30 +122,20 @@
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
             .compile()
             .writeToZip();
-    Path outputPath = temp.newFolder().toPath();
     ProcessResult compileResult =
         kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
             .addClasspathFiles(outputJar)
             .addSourceFiles(
                 getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
-            .setOutputPath(outputPath)
+            .setOutputPath(temp.newFolder().toPath())
             .compileRaw();
-    if (kotlinParameters.isNewerThan(KOTLINC_1_8_0)) {
-      testForJvm(parameters)
-          .addRunClasspathFiles(
-              kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), outputJar)
-          .addClasspath(outputPath)
-          .run(parameters.getRuntime(), PKG_APP + ".MainKt")
-          .assertSuccessWithOutputLines(
-              "foobar", "property oldName (Kotlin reflection is not available)");
-    } else {
-      Assert.assertEquals(1, compileResult.exitCode);
-      assertThat(
-          compileResult.stderr,
-          containsString(
-              "unsupported [reference to the synthetic extension property for a Java get/set"
-                  + " method]"));
-    }
+    Assert.assertEquals(1, compileResult.exitCode);
+    assertThat(
+        compileResult.stderr,
+        containsString(
+            kotlinParameters.isNewerThan(KOTLINC_1_8_0)
+                ? "references to synthetic java properties"
+                : "reference to the synthetic extension property"));
   }
 
   private void inspectMetadata(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java
index 4d76e2c..72a17ac 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTypeResultTest.java
@@ -4,20 +4,54 @@
 
 package com.android.tools.r8.retrace.api;
 
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
 import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowingFunction;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.PartitionMappingSupplierAsync;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceAsync;
+import com.android.tools.r8.retrace.RetraceAsyncResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
+import com.android.tools.r8.retrace.RetraceStackTraceResult;
 import com.android.tools.r8.retrace.RetraceTypeElement;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedMethodReference.KnownRetracedMethodReference;
 import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.retrace.StackTraceElementProxy;
+import com.android.tools.r8.retrace.StackTraceLineParser;
+import com.android.tools.r8.retrace.api.RetraceApiTypeResultTest.RetracePartitionStackTraceTest.IdentityStackTraceLineParser;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
+import org.junit.Assert;
 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 RetraceApiTypeResultTest extends RetraceApiTestBase {
@@ -64,4 +98,391 @@
       assertEquals(intArr, collect.get(0).getType().getTypeReference());
     }
   }
+
+  @RunWith(Parameterized.class)
+  public static class RetracePartitionStackTracePromiseTest extends TestBase {
+
+    @Parameters(name = "{0}")
+    public static TestParametersCollection data() {
+      return getTestParameters().withNoneRuntime().build();
+    }
+
+    public RetracePartitionStackTracePromiseTest(TestParameters parameters) {
+      parameters.assertNoneRuntime();
+    }
+
+    private final String MAPPING =
+        StringUtils.unixLines(
+            "com.Foo -> a:",
+            "  1:1:void m1():42:42 -> a",
+            "com.Bar -> b:",
+            "  2:2:void m2():43:43 -> b",
+            "com.Baz -> c:",
+            "  3:3:void m3():44:44 -> c");
+
+    public static class Promise<T> {
+
+      private final T value;
+
+      private Promise(T value) {
+        this.value = value;
+      }
+
+      public <R> Promise<R> then(ThrowingFunction<T, R, InterruptedException> f)
+          throws InterruptedException {
+        return new Promise<>(f.apply(value));
+      }
+
+      public <R> Promise<R> thenApply(ThrowingFunction<T, Promise<R>, InterruptedException> f)
+          throws InterruptedException {
+        return f.apply(value);
+      }
+    }
+
+    public static class PromiseRunner<T> {
+
+      public T run(Promise<T> promise) {
+        // Here we are cheating since we know the value is always available.
+        return promise.value;
+      }
+    }
+
+    public static class Storage {
+
+      private final byte[] metadata;
+      private final Map<String, byte[]> partitions;
+
+      public Storage(byte[] metadata, Map<String, byte[]> partitions) {
+        this.metadata = metadata;
+        this.partitions = partitions;
+      }
+
+      public Promise<byte[]> getMetadata() {
+        return new Promise<>(metadata);
+      }
+
+      public Promise<byte[]> getPartition(String key) {
+        return new Promise<>(partitions.getOrDefault(key, new byte[0]));
+      }
+    }
+
+    private Storage buildStorage(TestDiagnosticMessagesImpl diagnosticMessages) throws Exception {
+      Map<String, byte[]> partitions = new HashMap<>();
+      MappingPartitionMetadata metadataPromise =
+          ProguardMapPartitioner.builder(diagnosticMessages)
+              .setProguardMapProducer(ProguardMapProducer.fromString(MAPPING))
+              .setPartitionConsumer(
+                  partition -> partitions.put(partition.getKey(), partition.getPayload()))
+              .build()
+              .run();
+      return new Storage(metadataPromise.getBytes(), partitions);
+    }
+
+    private static <T, R> Promise<Map<T, R>> transpose(Map<T, Promise<R>> promiseMap)
+        throws InterruptedException {
+      Map<T, R> resolvedMap = new HashMap<>();
+      Box<InterruptedException> interruptedException = new Box<>();
+      promiseMap.forEach(
+          (key, promise) -> {
+            try {
+              promise.then(resolvedValue -> resolvedMap.put(key, resolvedValue));
+            } catch (InterruptedException e) {
+              interruptedException.set(e);
+            }
+          });
+      if (interruptedException.isSet()) {
+        throw interruptedException.get();
+      }
+      return new Promise<>(resolvedMap);
+    }
+
+    @Test
+    public void testRetrace() throws Exception {
+      TestDiagnosticMessagesImpl diagnosticMessages = new TestDiagnosticMessagesImpl();
+      Storage storage = buildStorage(diagnosticMessages);
+
+      List<StackTraceLine> minifiedStackTrace = new ArrayList<>();
+      minifiedStackTrace.add(StackTraceLine.parse("at a.a(SourceFile:1)"));
+      minifiedStackTrace.add(StackTraceLine.parse("at b.b(SourceFile:2)"));
+      minifiedStackTrace.add(StackTraceLine.parse("at c.c(SourceFile:3)"));
+
+      StackTrace retracedStacktrace =
+          new PromiseRunner<StackTrace>()
+              .run(
+                  storage
+                      .getMetadata()
+                      .thenApply(
+                          metadata -> {
+                            Map<String, Promise<byte[]>> partitionRequests = new HashMap<>();
+                            RetraceAsyncResult<RetraceStackTraceResult<StackTraceLine>>
+                                asyncResult =
+                                    RetraceAsync
+                                        .<StackTraceLine,
+                                            RetracePartitionStackTraceTest.StackTraceLineProxy>
+                                            builder()
+                                        .setStackTraceLineParser(new IdentityStackTraceLineParser())
+                                        .setDiagnosticsHandler(diagnosticMessages)
+                                        .setMappingSupplier(
+                                            PartitionMappingSupplierAsync.builder()
+                                                .setMetadata(metadata)
+                                                .setRegisterMappingPartitionCallback(
+                                                    key ->
+                                                        partitionRequests.put(
+                                                            key, storage.getPartition(key)))
+                                                .build())
+                                        .build()
+                                        .retraceStackTrace(
+                                            minifiedStackTrace, RetraceStackTraceContext.empty());
+                            return getThen(partitionRequests, asyncResult);
+                          }));
+      StackTrace expectedStackTrace =
+          StackTrace.builder()
+              .add(
+                  StackTraceLine.builder()
+                      .setClassName("com.Foo")
+                      .setMethodName("m1")
+                      .setFileName("Foo.java")
+                      .setLineNumber(42)
+                      .build())
+              .add(
+                  StackTraceLine.builder()
+                      .setClassName("com.Bar")
+                      .setMethodName("m2")
+                      .setFileName("Bar.java")
+                      .setLineNumber(43)
+                      .build())
+              .add(
+                  StackTraceLine.builder()
+                      .setClassName("com.Baz")
+                      .setMethodName("m3")
+                      .setFileName("Baz.java")
+                      .setLineNumber(44)
+                      .build())
+              .build();
+      assertThat(retracedStacktrace, isSame(expectedStackTrace));
+    }
+
+    // This method needs to be outlined due to a bug in javac 8.
+    private Promise<StackTrace> getThen(
+        Map<String, Promise<byte[]>> partitionRequests,
+        RetraceAsyncResult<RetraceStackTraceResult<StackTraceLine>> asyncResult)
+        throws InterruptedException {
+      return transpose(partitionRequests)
+          .then(
+              resolvedPartitions -> {
+                StackTrace.Builder retraceStackTraceBuilder = StackTrace.builder();
+                asyncResult
+                    .getResult(resolvedPartitions::get)
+                    .forEach(
+                        retraced -> {
+                          Assert.assertEquals(1, retraced.size());
+                          retraced.get(0).forEach(retraceStackTraceBuilder::add);
+                        });
+                return retraceStackTraceBuilder.build();
+              });
+    }
+  }
+
+  @RunWith(Parameterized.class)
+  public static class RetracePartitionStackTraceTest extends TestBase {
+
+    @Parameters(name = "{0}")
+    public static TestParametersCollection data() {
+      return getTestParameters().withNoneRuntime().build();
+    }
+
+    public RetracePartitionStackTraceTest(TestParameters parameters) {
+      parameters.assertNoneRuntime();
+    }
+
+    private final String MAPPING =
+        StringUtils.unixLines(
+            "com.Foo -> a:",
+            "  1:1:void m1():42:42 -> a",
+            "com.Bar -> b:",
+            "  2:2:void m2():43:43 -> b",
+            "com.Baz -> c:",
+            "  3:3:void m3():44:44 -> c");
+
+    @Test
+    public void testRetrace() throws Exception {
+      TestDiagnosticMessagesImpl diagnosticMessages = new TestDiagnosticMessagesImpl();
+      Map<String, byte[]> partitions = new HashMap<>();
+      MappingPartitionMetadata metadata =
+          ProguardMapPartitioner.builder(diagnosticMessages)
+              .setProguardMapProducer(ProguardMapProducer.fromString(MAPPING))
+              .setPartitionConsumer(
+                  partition -> partitions.put(partition.getKey(), partition.getPayload()))
+              .build()
+              .run();
+
+      Set<String> requestedKeys = new HashSet<>();
+      BooleanBox setPartitionCallBack = new BooleanBox();
+      PartitionMappingSupplier mappingSupplier =
+          PartitionMappingSupplier.builder()
+              .setMetadata(metadata.getBytes())
+              .setRegisterMappingPartitionCallback(requestedKeys::add)
+              .setPrepareMappingPartitionsCallback(setPartitionCallBack::set)
+              .setMappingPartitionFromKeySupplier(partitions::get)
+              .build();
+
+      Retrace<StackTraceLine, StackTraceLineProxy> retrace =
+          Retrace.<StackTraceLine, StackTraceLineProxy>builder()
+              .setMappingSupplier(mappingSupplier)
+              .setStackTraceLineParser(StackTraceLineProxy::new)
+              .setDiagnosticsHandler(diagnosticMessages)
+              .build();
+
+      List<StackTraceLine> minifiedStackTrace = new ArrayList<>();
+      minifiedStackTrace.add(StackTraceLine.parse("at a.a(SourceFile:1)"));
+      minifiedStackTrace.add(StackTraceLine.parse("at b.b(SourceFile:2)"));
+      minifiedStackTrace.add(StackTraceLine.parse("at c.c(SourceFile:3)"));
+      StackTrace.Builder retraceStackTraceBuilder = StackTrace.builder();
+      retrace
+          .retraceStackTrace(minifiedStackTrace, RetraceStackTraceContext.empty())
+          .forEach(
+              stackTraceLines -> {
+                Assert.assertEquals(1, stackTraceLines.size());
+                stackTraceLines.get(0).forEach(retraceStackTraceBuilder::add);
+              });
+      Assert.assertEquals(partitions.keySet(), requestedKeys);
+      StackTrace expectedStackTrace =
+          StackTrace.builder()
+              .add(
+                  StackTraceLine.builder()
+                      .setClassName("com.Foo")
+                      .setMethodName("m1")
+                      .setFileName("Foo.java")
+                      .setLineNumber(42)
+                      .build())
+              .add(
+                  StackTraceLine.builder()
+                      .setClassName("com.Bar")
+                      .setMethodName("m2")
+                      .setFileName("Bar.java")
+                      .setLineNumber(43)
+                      .build())
+              .add(
+                  StackTraceLine.builder()
+                      .setClassName("com.Baz")
+                      .setMethodName("m3")
+                      .setFileName("Baz.java")
+                      .setLineNumber(44)
+                      .build())
+              .build();
+      assertThat(expectedStackTrace, isSame(retraceStackTraceBuilder.build()));
+    }
+
+    public static class IdentityStackTraceLineParser
+        implements StackTraceLineParser<StackTraceLine, StackTraceLineProxy> {
+
+      @Override
+      public StackTraceLineProxy parse(StackTraceLine stackTraceLine) {
+        return new StackTraceLineProxy(stackTraceLine);
+      }
+    }
+
+    public static class StackTraceLineProxy
+        extends StackTraceElementProxy<StackTraceLine, StackTraceLineProxy> {
+
+      private final StackTraceLine stackTraceLine;
+
+      public StackTraceLineProxy(StackTraceLine stackTraceLine) {
+        this.stackTraceLine = stackTraceLine;
+      }
+
+      @Override
+      public boolean hasClassName() {
+        return true;
+      }
+
+      @Override
+      public boolean hasMethodName() {
+        return true;
+      }
+
+      @Override
+      public boolean hasSourceFile() {
+        return true;
+      }
+
+      @Override
+      public boolean hasLineNumber() {
+        return true;
+      }
+
+      @Override
+      public boolean hasFieldName() {
+        return false;
+      }
+
+      @Override
+      public boolean hasFieldOrReturnType() {
+        return false;
+      }
+
+      @Override
+      public boolean hasMethodArguments() {
+        return false;
+      }
+
+      @Override
+      public ClassReference getClassReference() {
+        return Reference.classFromTypeName(stackTraceLine.className);
+      }
+
+      @Override
+      public String getMethodName() {
+        return stackTraceLine.methodName;
+      }
+
+      @Override
+      public String getSourceFile() {
+        return stackTraceLine.fileName;
+      }
+
+      @Override
+      public int getLineNumber() {
+        return stackTraceLine.lineNumber;
+      }
+
+      @Override
+      public String getFieldName() {
+        return null;
+      }
+
+      @Override
+      public String getFieldOrReturnType() {
+        return null;
+      }
+
+      @Override
+      public String getMethodArguments() {
+        return null;
+      }
+
+      @Override
+      public StackTraceLine toRetracedItem(
+          RetraceStackTraceElementProxy<StackTraceLine, StackTraceLineProxy> retracedProxy,
+          boolean verbose) {
+        RetracedMethodReference retracedMethod = retracedProxy.getRetracedMethod();
+        if (retracedMethod == null) {
+          return new StackTraceLine(
+              stackTraceLine.toString(),
+              stackTraceLine.className,
+              stackTraceLine.methodName,
+              stackTraceLine.fileName,
+              stackTraceLine.lineNumber);
+        } else {
+          KnownRetracedMethodReference knownRetracedMethodReference = retracedMethod.asKnown();
+          return new StackTraceLine(
+              stackTraceLine.toString(),
+              knownRetracedMethodReference.getMethodReference().getHolderClass().getTypeName(),
+              knownRetracedMethodReference.getMethodName(),
+              retracedProxy.getSourceFile(),
+              knownRetracedMethodReference.getOriginalPositionOrDefault(0));
+        }
+      }
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStackTraceTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStackTraceTest.java
deleted file mode 100644
index 671d075..0000000
--- a/src/test/java/com/android/tools/r8/retrace/api/RetracePartitionStackTraceTest.java
+++ /dev/null
@@ -1,234 +0,0 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.retrace.api;
-
-import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestDiagnosticMessagesImpl;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.MappingPartitionMetadata;
-import com.android.tools.r8.retrace.PartitionMappingSupplier;
-import com.android.tools.r8.retrace.ProguardMapPartitioner;
-import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.Retrace;
-import com.android.tools.r8.retrace.RetraceStackTraceContext;
-import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
-import com.android.tools.r8.retrace.RetracedMethodReference.KnownRetracedMethodReference;
-import com.android.tools.r8.retrace.StackTraceElementProxy;
-import com.android.tools.r8.retrace.StackTraceLineParser;
-import com.android.tools.r8.utils.BooleanBox;
-import com.android.tools.r8.utils.StringUtils;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class RetracePartitionStackTraceTest extends TestBase {
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public RetracePartitionStackTraceTest(TestParameters parameters) {
-    parameters.assertNoneRuntime();
-  }
-
-  private final String MAPPING =
-      StringUtils.unixLines(
-          "com.Foo -> a:",
-          "  1:1:void m1():42:42 -> a",
-          "com.Bar -> b:",
-          "  2:2:void m2():43:43 -> b",
-          "com.Baz -> c:",
-          "  3:3:void m3():44:44 -> c");
-
-  @Test
-  public void testRetrace() throws Exception {
-    TestDiagnosticMessagesImpl diagnosticMessages = new TestDiagnosticMessagesImpl();
-    Map<String, byte[]> partitions = new HashMap<>();
-    MappingPartitionMetadata metadata =
-        ProguardMapPartitioner.builder(diagnosticMessages)
-            .setProguardMapProducer(ProguardMapProducer.fromString(MAPPING))
-            .setPartitionConsumer(
-                partition -> partitions.put(partition.getKey(), partition.getPayload()))
-            .build()
-            .run();
-
-    Set<String> requestedKeys = new HashSet<>();
-    BooleanBox setPartitionCallBack = new BooleanBox();
-    PartitionMappingSupplier mappingSupplier =
-        PartitionMappingSupplier.builder()
-            .setMetadata(metadata.getBytes())
-            .setRegisterMappingPartitionCallback(requestedKeys::add)
-            .setPrepareMappingPartitionsCallback(setPartitionCallBack::set)
-            .setMappingPartitionFromKeySupplier(partitions::get)
-            .build();
-
-    Retrace<StackTraceLine, StackTraceLineProxy> retrace =
-        Retrace.<StackTraceLine, StackTraceLineProxy>builder()
-            .setMappingSupplier(mappingSupplier)
-            .setStackTraceLineParser(StackTraceLineProxy::new)
-            .setDiagnosticsHandler(diagnosticMessages)
-            .build();
-
-    List<StackTraceLine> minifiedStackTrace = new ArrayList<>();
-    minifiedStackTrace.add(StackTraceLine.parse("at a.a(SourceFile:1)"));
-    minifiedStackTrace.add(StackTraceLine.parse("at b.b(SourceFile:2)"));
-    minifiedStackTrace.add(StackTraceLine.parse("at c.c(SourceFile:3)"));
-    StackTrace.Builder retraceStackTraceBuilder = StackTrace.builder();
-    retrace
-        .retraceStackTrace(minifiedStackTrace, RetraceStackTraceContext.empty())
-        .forEach(
-            stackTraceLines -> {
-              assertEquals(1, stackTraceLines.size());
-              stackTraceLines.get(0).forEach(retraceStackTraceBuilder::add);
-            });
-    assertEquals(partitions.keySet(), requestedKeys);
-    StackTrace expectedStackTrace =
-        StackTrace.builder()
-            .add(
-                StackTraceLine.builder()
-                    .setClassName("com.Foo")
-                    .setMethodName("m1")
-                    .setFileName("Foo.java")
-                    .setLineNumber(42)
-                    .build())
-            .add(
-                StackTraceLine.builder()
-                    .setClassName("com.Bar")
-                    .setMethodName("m2")
-                    .setFileName("Bar.java")
-                    .setLineNumber(43)
-                    .build())
-            .add(
-                StackTraceLine.builder()
-                    .setClassName("com.Baz")
-                    .setMethodName("m3")
-                    .setFileName("Baz.java")
-                    .setLineNumber(44)
-                    .build())
-            .build();
-    assertThat(expectedStackTrace, isSame(retraceStackTraceBuilder.build()));
-  }
-
-  public static class IdentityStackTraceLineParser
-      implements StackTraceLineParser<StackTraceLine, StackTraceLineProxy> {
-
-    @Override
-    public StackTraceLineProxy parse(StackTraceLine stackTraceLine) {
-      return new StackTraceLineProxy(stackTraceLine);
-    }
-  }
-
-  public static class StackTraceLineProxy
-      extends StackTraceElementProxy<StackTraceLine, StackTraceLineProxy> {
-
-    private final StackTraceLine stackTraceLine;
-
-    public StackTraceLineProxy(StackTraceLine stackTraceLine) {
-      this.stackTraceLine = stackTraceLine;
-    }
-
-    @Override
-    public boolean hasClassName() {
-      return true;
-    }
-
-    @Override
-    public boolean hasMethodName() {
-      return true;
-    }
-
-    @Override
-    public boolean hasSourceFile() {
-      return true;
-    }
-
-    @Override
-    public boolean hasLineNumber() {
-      return true;
-    }
-
-    @Override
-    public boolean hasFieldName() {
-      return false;
-    }
-
-    @Override
-    public boolean hasFieldOrReturnType() {
-      return false;
-    }
-
-    @Override
-    public boolean hasMethodArguments() {
-      return false;
-    }
-
-    @Override
-    public ClassReference getClassReference() {
-      return Reference.classFromTypeName(stackTraceLine.className);
-    }
-
-    @Override
-    public String getMethodName() {
-      return stackTraceLine.methodName;
-    }
-
-    @Override
-    public String getSourceFile() {
-      return stackTraceLine.fileName;
-    }
-
-    @Override
-    public int getLineNumber() {
-      return stackTraceLine.lineNumber;
-    }
-
-    @Override
-    public String getFieldName() {
-      return null;
-    }
-
-    @Override
-    public String getFieldOrReturnType() {
-      return null;
-    }
-
-    @Override
-    public String getMethodArguments() {
-      return null;
-    }
-
-    @Override
-    public StackTraceLine toRetracedItem(
-        RetraceStackTraceElementProxy<StackTraceLine, StackTraceLineProxy> retracedProxy,
-        boolean verbose) {
-      KnownRetracedMethodReference knownRetracedMethodReference =
-          retracedProxy.getRetracedMethod().asKnown();
-      return new StackTraceLine(
-          stackTraceLine.toString(),
-          knownRetracedMethodReference.getMethodReference().getHolderClass().getTypeName(),
-          knownRetracedMethodReference.getMethodName(),
-          retracedProxy.getSourceFile(),
-          knownRetracedMethodReference.getOriginalPositionOrDefault(0));
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/R8ZipContainerMappingFileTest.java b/src/test/java/com/android/tools/r8/retrace/partition/R8ZipContainerMappingFileTest.java
index 211121c..7c64527 100644
--- a/src/test/java/com/android/tools/r8/retrace/partition/R8ZipContainerMappingFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/partition/R8ZipContainerMappingFileTest.java
@@ -9,14 +9,15 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProguardMapConsumer;
+import com.android.tools.r8.PartitionMapConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.naming.ProguardMapPartitionConsumer;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.retrace.MappingPartition;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
 import com.android.tools.r8.retrace.PartitionMappingSupplier;
 import com.android.tools.r8.retrace.partition.testclasses.R8ZipContainerMappingFileTestClasses;
 import com.android.tools.r8.retrace.partition.testclasses.R8ZipContainerMappingFileTestClasses.Main;
@@ -79,8 +80,6 @@
   public void testR8() throws Exception {
     Path pgMapFile = temp.newFile("mapping.zip").toPath();
     DiagnosticsHandler diagnosticsHandler = new TestDiagnosticMessagesImpl();
-    ProguardMapConsumer partitionZipConsumer =
-        createPartitionZipConsumer(pgMapFile, diagnosticsHandler);
     StackTrace originalStackTrace =
         testForR8(parameters.getBackend())
             .addInnerClasses(R8ZipContainerMappingFileTestClasses.class)
@@ -88,7 +87,7 @@
             .addKeepMainRule(Main.class)
             .addKeepAttributeSourceFile()
             .addKeepAttributeLineNumberTable()
-            .addOptionsModification(options -> options.proguardMapConsumer = partitionZipConsumer)
+            .setPartitionMapConsumer(createPartitionZipConsumer(pgMapFile))
             .run(parameters.getRuntime(), Main.class)
             .assertFailureWithErrorThatThrows(RuntimeException.class)
             .getOriginalStackTrace();
@@ -99,36 +98,37 @@
         isSame(EXPECTED));
   }
 
-  private ProguardMapConsumer createPartitionZipConsumer(
-      Path pgMapFile, DiagnosticsHandler diagnosticsHandler) throws IOException {
+  private PartitionMapConsumer createPartitionZipConsumer(Path pgMapFile) throws IOException {
     ZipBuilder zipBuilder = ZipBuilder.builder(pgMapFile);
-    return ProguardMapPartitionConsumer.builder()
-        .setMappingPartitionConsumer(
-            mappingPartition -> {
-              try {
-                zipBuilder.addBytes(mappingPartition.getKey(), mappingPartition.getPayload());
-              } catch (IOException e) {
-                throw new RuntimeException(e);
-              }
-            })
-        .setMetadataConsumer(
-            metadata -> {
-              try {
-                zipBuilder.addBytes("METADATA", metadata.getBytes());
-              } catch (IOException e) {
-                throw new RuntimeException(e);
-              }
-            })
-        .setFinishedConsumer(
-            () -> {
-              try {
-                zipBuilder.build();
-              } catch (IOException e) {
-                throw new RuntimeException(e);
-              }
-            })
-        .setDiagnosticsHandler(diagnosticsHandler)
-        .build();
+    return new PartitionMapConsumer() {
+      @Override
+      public void acceptMappingPartition(MappingPartition mappingPartition) {
+        try {
+          zipBuilder.addBytes(mappingPartition.getKey(), mappingPartition.getPayload());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+
+      @Override
+      public void acceptMappingPartitionMetadata(
+          MappingPartitionMetadata mappingPartitionMetadata) {
+        try {
+          zipBuilder.addBytes("METADATA", mappingPartitionMetadata.getBytes());
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        try {
+          zipBuilder.build();
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
   }
 
   private PartitionMappingSupplier createMappingSupplierFromPartitionZip(Path pgMapFile)
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 81c7400..21a12a2 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
@@ -34,6 +34,7 @@
 import com.android.tools.r8.naming.signature.GenericSignatureAction;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileOptions;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -123,6 +124,7 @@
     dexItemFactory = options.itemFactory;
     AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
     application = new ApplicationReader(input, options, timing).read();
+    configureOptions();
   }
 
   public CodeInspector(AndroidApp app) throws IOException {
@@ -184,6 +186,12 @@
       obfuscatedToOriginalMapping = nameMapping.original;
       originalToObfuscatedMapping = nameMapping.inverse;
     }
+    configureOptions();
+  }
+
+  private void configureOptions() {
+    ArtProfileOptions artProfileOptions = application.options.getArtProfileOptions();
+    artProfileOptions.setAllowReadingEmptyArtProfileProvidersMultipleTimesForTesting(true);
   }
 
   public DexApplication getApplication() {
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index ed13628..5678440 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-f9e5287651f7663c7ebd2f14a58be99eb238bd8a
\ No newline at end of file
+5c0b406a52a08af4318a104660091c61d6bb8446
\ No newline at end of file
diff --git a/tools/trigger.py b/tools/trigger.py
index d716a55..0fd8b64 100755
--- a/tools/trigger.py
+++ b/tools/trigger.py
@@ -25,6 +25,7 @@
 DESUGAR_JDK11_BOT = 'lib_desugar-archive-jdk11'
 DESUGAR_JDK11_LEGACY_BOT = 'lib_desugar-archive-jdk11-legacy'
 DESUGAR_JDK8_BOT = 'lib_desugar-archive-jdk8'
+SMALI_BOT = 'smali'
 
 def ParseOptions():
   result = optparse.OptionParser()
@@ -32,6 +33,7 @@
                     help='Run on the release branch builders.',
                     default=False, action='store_true')
   result.add_option('--cl',
+                    metavar=('<url>'),
                     help='Run the specified cl on the bots. This should be '
                     'the full url, e.g., '
                     'https://r8-review.googlesource.com/c/r8/+/37420/1')
@@ -44,6 +46,9 @@
   result.add_option('--desugar-jdk8',
                     help='Run the jdk8 library desugar and archiving bot.',
                     default=False, action='store_true')
+  result.add_option('--smali',
+                    metavar=('<version>'),
+                    help='Build smali version <version>.')
 
   result.add_option('--builder', help='Trigger specific builder')
   return result.parse_args()
@@ -72,6 +77,7 @@
   print('Desugar jdk11 builder:\n  ' + DESUGAR_JDK11_BOT)
   print('Desugar jdk11 legacy builder:\n  ' + DESUGAR_JDK11_LEGACY_BOT)
   print('Desugar jdk8 builder:\n  ' + DESUGAR_JDK8_BOT)
+  print('Smali builder:\n  ' + SMALI_BOT)
   print('Main builders:\n  ' + '\n  '.join(main_builders))
   print('Release builders:\n  ' + '\n  '.join(release_builders))
   return (main_builders, release_builders)
@@ -88,6 +94,20 @@
     cmd = ['bb', 'add', 'r8/ci/%s' % builder , '-commit', commit_url]
     subprocess.check_call(cmd)
 
+def trigger_smali_builder(version):
+  utils.check_basic_semver_version(
+    version,
+    'use semantic version of the smali version to built (pre-releases are not supported)',
+    allowPrerelease = False)
+  cmd = [
+      'bb',
+      'add',
+      'r8/ci/%s' % SMALI_BOT,
+      '-p',
+      '\'test_options=["--version", "%s"]\'' % version
+  ]
+  subprocess.check_call(cmd)
+
 def trigger_cl(builders, cl_url):
   for builder in builders:
     cmd = ['bb', 'add', 'r8/ci/%s' % builder , '-cl', cl_url]
@@ -96,7 +116,8 @@
 def Main():
   (options, args) = ParseOptions()
   desugar = options.desugar_jdk11 or options.desugar_jdk11_legacy or options.desugar_jdk8
-  if len(args) != 1 and not options.cl and not desugar:
+  requires_commit = not options.cl and not desugar and not options.smali
+  if len(args) != 1 and requires_commit:
     print('Takes exactly one argument, the commit to run')
     return 1
 
@@ -108,7 +129,19 @@
     print('You can\'t run cls on the desugar bot')
     return 1
 
-  commit = None if (options.cl or desugar)  else args[0]
+  if options.cl and options.smali:
+    print('You can\'t run cls on the smali bot')
+    return 1
+
+  if options.smali:
+    if not options.release:
+      print('Only release versions of smali can be built')
+      return 1
+
+    trigger_smali_builder(options.smali)
+    return
+
+  commit = None if not requires_commit else args[0]
   (main_builders, release_builders) = get_builders()
   builders = release_builders if options.release else main_builders
   if options.builder: