Merge commit 'ef959617f3f98e98e51b527ebc56dc0a33bafc87' into dev-release
diff --git a/README.md b/README.md
index d4f6295..0a085c1 100644
--- a/README.md
+++ b/README.md
@@ -81,36 +81,74 @@
 
 Debug mode build:
 
-    $ java -cp build/libs/r8.jar com.android.tools.r8.D8  --output out input.jar
+    $ java -cp build/libs/r8.jar com.android.tools.r8.D8 \
+           --min-api <min-api> \
+           --output out \
+           --lib <android.jar/rt.jar> \
+           input.jar
 
 Release mode build:
 
-    $ java -cp build/libs/r8.jar com.android.tools.r8.D8 --release --output out input.jar
+    $ java -cp build/libs/r8.jar com.android.tools.r8.D8
+           --release \
+           --min-api <min-api> \
+           --output out \
+           --lib <android.jar/rt.jar> \
+           input.jar
+
+See [Running R8](#running-r8) for information on options `--min-api` and `--lib`.
 
 The full set of D8 options can be obtained by running the command line tool with
 the `--help` option.
 
 ## <a name="running-r8"></a>Running R8
 
-R8 is a [ProGuard](https://www.guardsquare.com/en/proguard) replacement for
-whole-program optimization, shrinking and minification. R8 uses the ProGuard
-keep rule format for specifying the entry points for an application.
+R8 is a whole-program optimizing compiler with focus on shrinking the size of
+programs. R8 uses the
+[ProGuard configuration format](https://www.guardsquare.com/manual/configuration/usage)
+for configuring the whole-program optimization including specifying the entry points
+for an application.
 
 R8 consumes class files and can output either DEX for Android apps or class files
 for Java apps.
 
 Typical invocations of R8 to produce optimized DEX file(s) in the `out` directory:
 
-    $ java -cp build/libs/r8.jar com.android.tools.r8.R8 --release --output out --pg-conf proguard.cfg input.jar
+    $ java -cp build/libs/r8.jar com.android.tools.r8.R8 \
+           --release \
+           --min-api <min-api> \
+           --output out \
+           --pg-conf proguard.cfg \
+           --lib <android.jar/rt.jar> \
+           input.jar
 
- The default is to produce DEX. To produce class files pass the option `--classfile`. This invocation will provide optimized Java classfiles in `output.jar`:
+This produce DEX targeting Android devices with a API level of `<min-api>` and above.
 
-    $ java -cp build/libs/r8.jar com.android.tools.r8.R8 --classfile --release --output output.jar --pg-conf proguard.cfg input.jar
+The option `--lib` is passing the bootclasspath for the targeted runtime.
+For targeting Android use an `android.jar` from the Android Platform SDK, typically
+located in `~/Android/Sdk/platforms/android-XX`, where `XX` should be the latest
+Android version.
+
+To produce class files pass the option `--classfile` and leave out `--min-api <min-api>`.
+This invocation will provide optimized Java classfiles in `output.jar`:
+
+    $ java -cp build/libs/r8.jar com.android.tools.r8.R8 \
+           --release \
+           --classfile \
+           --output output.jar \
+           --pg-conf proguard.cfg \
+           --lib <android.jar/rt.jar> \
+           input.jar
+
+
+When producing output targeted for the JVM one can pass `$JAVA_HOME` to `-lib`.
+R8 will then locate the Java bootclasspath from there.
 
 The full set of R8 options can be obtained by running the command line tool with
 the `--help` option.
 
-R8 is not command line compatible with ProGuard, for instance keep rules cannot be passed on the command line, but have to be passed in a file using the `--pg-conf` option.
+R8 is not command line compatible with ProGuard, for instance keep rules cannot be passed
+on the command line, but have to be passed in a file using the `--pg-conf` option.
 
 ## Testing
 
diff --git a/build.gradle b/build.gradle
index fe2980a..85e3fc6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,7 +29,7 @@
 
 ext {
     androidSupportVersion = '25.4.0'
-    asmVersion = '9.4'  // When updating update tools/asmifier.py, build.src and Toolhelper as well.
+    asmVersion = '9.5'  // When updating update tools/asmifier.py, build.src and Toolhelper as well.
     javassistVersion = '3.29.2-GA'
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 88e29ab..bfe46c2 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -9,7 +9,7 @@
 }
 
 ext {
-    asmVersion = '9.4'
+    asmVersion = '9.5'
 }
 
 dependencies {
diff --git a/compatibility-faq.md b/compatibility-faq.md
index 5614f6f..f0d9c05 100644
--- a/compatibility-faq.md
+++ b/compatibility-faq.md
@@ -30,6 +30,20 @@
 Additionally, for attributes describing a relationship such as `InnerClass` and
 `EnclosingMethod`, non-compat mode requires both endpoints being kept.
 
+## Stack traces and retracing
+When compiling with R8, a mapping file can be produced to support mapping stack
+traces of the R8 optimized program back to the original source information.
+
+Starting with R8 version 8.2, the compiler will retain full original source file
+names in the mapping information without the need to specify
+`-keepattributes SourceFile`.
+
+In addition, builds that target API level 26 or above (and don't specify custom
+source-file information) will also retain mapping information for all lines
+without the need to specify `-keepattributes LineNumberTable`.
+
+More information on R8 mapping files can be found in the [retrace doc](doc/retrace.md).
+
 # Troubleshooting
 
 The rest of this document describes known issues with libraries that use
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 667ecc9..5558506 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -165,6 +165,7 @@
       "api_level_below_or_equal": 25,
       "rewrite_prefix": {
         "java.time.": "j$.time.",
+        "java.util.Base64": "j$.util.Base64",
         "java.util.Desugar": "j$.util.Desugar"
       },
       "dont_rewrite_prefix": [
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 890f3f9..c22cadd 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -22,7 +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.PartitionMapZipContainer;
 import com.android.tools.r8.utils.ProgramConsumerUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -403,7 +403,8 @@
      */
     public B setPartitionMapOutputPath(Path partitionMapOutput) {
       assert partitionMapOutput != null;
-      return setPartitionMapConsumer(MapConsumerUtils.createZipConsumer(partitionMapOutput));
+      return setPartitionMapConsumer(
+          PartitionMapZipContainer.createPartitionMapZipContainerConsumer(partitionMapOutput));
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 2e44e14..b6eae99 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.bisect.Bisect;
 import com.android.tools.r8.cf.CfVerifierTool;
 import com.android.tools.r8.compatproguard.CompatProguard;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.DesugaredMethodsList;
 import com.android.tools.r8.relocator.RelocatorCommandLine;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import java.util.Arrays;
@@ -64,6 +65,9 @@
       case "backportedmethods":
         BackportedMethodList.main(shift(args));
         break;
+      case "desugaredmethods":
+        DesugaredMethodsList.main(shift(args));
+        break;
       case "relocator":
         RelocatorCommandLine.main(shift(args));
         break;
diff --git a/src/main/java/com/android/tools/r8/cf/CfVersion.java b/src/main/java/com/android/tools/r8/cf/CfVersion.java
index 1fd5ce7..f69e532 100644
--- a/src/main/java/com/android/tools/r8/cf/CfVersion.java
+++ b/src/main/java/com/android/tools/r8/cf/CfVersion.java
@@ -44,6 +44,8 @@
   public static final CfVersion V19_PREVIEW = new CfVersion(Opcodes.V19 | Opcodes.V_PREVIEW);
   public static final CfVersion V20 = new CfVersion(Opcodes.V20);
   public static final CfVersion V20_PREVIEW = new CfVersion(Opcodes.V20 | Opcodes.V_PREVIEW);
+  public static final CfVersion V21 = new CfVersion(Opcodes.V21);
+  public static final CfVersion V21_PREVIEW = new CfVersion(Opcodes.V21 | Opcodes.V_PREVIEW);
 
   private final int version;
 
@@ -67,7 +69,8 @@
     CfVersion.V17,
     CfVersion.V18,
     CfVersion.V19,
-    CfVersion.V20
+    CfVersion.V20,
+    CfVersion.V21
   };
 
   // Private constructor in case we want to canonicalize versions.
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index dbed84f..4df769b 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrameVerifier;
 import com.android.tools.r8.cf.code.CfFrameVerifier.StackMapStatus;
 import com.android.tools.r8.cf.code.CfFrameVerifierEventConsumer;
@@ -417,24 +416,6 @@
     return preamble;
   }
 
-  private static class PrunePreambleMethodVisitor extends MethodVisitor {
-
-    private boolean inPreamble = true;
-
-    public PrunePreambleMethodVisitor(MethodVisitor methodVisitor) {
-      super(InternalOptions.ASM_VERSION, methodVisitor);
-    }
-
-    @Override
-    public void visitLineNumber(int line, Label start) {
-      if (line == 0 && inPreamble) {
-        return;
-      }
-      inPreamble = false;
-      super.visitLineNumber(line, start);
-    }
-  }
-
   @Override
   public void writeCf(
       ProgramMethod method,
@@ -460,20 +441,20 @@
             || (appView.enableWholeProgramOptimizations()
                 && classFileVersion.isEqualTo(CfVersion.V1_6)
                 && !options.shouldKeepStackMapTable());
-    PrunePreambleMethodVisitor prunePreambleVisitor = new PrunePreambleMethodVisitor(visitor);
+    Position preamblePosition = getPreamblePosition();
+    boolean discardPreamble = preamblePosition != null && preamblePosition.isSyntheticPosition();
     for (CfInstruction instruction : instructions) {
-      if (discardFrames && instruction instanceof CfFrame) {
+      if (discardFrames && instruction.isFrame()) {
+        continue;
+      }
+      if (discardPreamble
+          && instruction.isPosition()
+          && instruction.asPosition().getPosition().equals(preamblePosition)) {
+        discardPreamble = false;
         continue;
       }
       instruction.write(
-          appView,
-          method,
-          dexItemFactory,
-          graphLens,
-          initClassLens,
-          namingLens,
-          rewriter,
-          prunePreambleVisitor);
+          appView, method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 97dfaf5..097aa59 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -471,7 +471,6 @@
           clazz -> {
             DexType type = lens.lookupType(clazz.type);
             if (type.isPrimitiveType()) {
-              assert !objectAllocationInfos.hasInstantiatedStrictSubtype(clazz);
               return;
             }
             DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 680ace0..9834a17 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -18,6 +18,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 java.util.Set;
 
 public class InstanceOf extends Instruction {
@@ -135,4 +136,9 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerInstanceOf(type);
   }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addInstanceOf(type, value());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
index 15712e1..de5ce5a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.utils.IntObjConsumer;
 import com.android.tools.r8.utils.InternalOutputMode;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -35,6 +36,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addIntSwitch(value(), getKeys(), getKeyToTargetMap(), fallthroughBlock());
+  }
+
+  @Override
   public int opcode() {
     return Opcodes.INT_SWITCH;
   }
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 cf7a34f..588ce91 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
@@ -789,6 +789,9 @@
       while (it.hasNext()) {
         Instruction instruction = it.next();
         Position position = instruction.getPosition();
+        if (instruction.isArgument()) {
+          continue;
+        }
         if (instruction.isMoveException()) {
           assert current == null;
           current = position;
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 dc4e201..e5ad61c 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
@@ -15,7 +15,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -42,26 +41,26 @@
   final MachineDesugaredLibrarySpecification desugaredLibrarySpecification;
   final Path desugaredLibrarySpecificationPath;
   final Collection<Path> desugaredLibraryImplementation;
-  final Path outputDirectory;
+  final Path output;
   final Path androidJar;
 
   AbstractGenerateFiles(
       String desugarConfigurationPath,
       String desugarImplementationPath,
-      String outputDirectory,
+      String output,
       String androidJarPath)
       throws Exception {
     this(
         Paths.get(desugarConfigurationPath),
         ImmutableList.of(Paths.get(desugarImplementationPath)),
-        Paths.get(outputDirectory),
+        Paths.get(output),
         Paths.get(androidJarPath));
   }
 
   AbstractGenerateFiles(
       Path desugarConfigurationPath,
       Collection<Path> desugarImplementationPath,
-      Path outputDirectory,
+      Path output,
       Path androidJar)
       throws Exception {
     assert androidJar != null;
@@ -72,10 +71,7 @@
     DexApplication app = createApp(androidJar, options);
     this.desugaredLibrarySpecification = specification.toMachineSpecification(app, Timing.empty());
     this.desugaredLibraryImplementation = desugarImplementationPath;
-    this.outputDirectory = outputDirectory;
-    if (!Files.isDirectory(this.outputDirectory)) {
-      throw new Exception("Output directory " + outputDirectory + " is not a directory");
-    }
+    this.output = output;
   }
 
   private DesugaredLibrarySpecification readDesugaredLibraryConfiguration(
@@ -118,28 +114,9 @@
     return jar;
   }
 
-  private static String getAndroidJarPath(String[] args, int fullLength) {
+  static String getAndroidJarPath(String[] args, int fullLength) {
     return args.length == fullLength
         ? args[fullLength - 1]
         : getFallBackAndroidJarPath(MAX_TESTED_ANDROID_API_LEVEL);
   }
-
-  public static void main(String[] args) throws Exception {
-    if (args[0].equals("--generate-api-docs")) {
-      if (args.length == 4 || args.length == 5) {
-        new GenerateHtmlDoc(args[1], args[2], args[3], getAndroidJarPath(args, 5)).run();
-        return;
-      }
-    } else if (args.length == 3 || args.length == 4) {
-      new GenerateLintFiles(args[0], args[1], args[2], getAndroidJarPath(args, 4)).run();
-      return;
-    }
-    throw new RuntimeException(
-        StringUtils.joinLines(
-            "Invalid invocation.",
-            "Usage: AbstractGenerateFiles [--generate-api-docs] <desugar configuration> <desugar"
-                + " implementation> <output directory> [<android jar path for Android "
-                + MAX_TESTED_ANDROID_API_LEVEL
-                + " or higher>]"));
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
new file mode 100644
index 0000000..0a1a614
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/DesugaredMethodsList.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
+
+import static java.lang.Integer.parseInt;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+
+@Keep
+public class DesugaredMethodsList extends GenerateDesugaredLibraryLintFiles {
+
+  private final AndroidApiLevel minApi;
+
+  private DesugaredMethodsList(
+      int minApi,
+      String desugarConfigurationPath,
+      String desugarImplementationPath,
+      String ouputFile,
+      String androidJarPath)
+      throws Exception {
+    super(desugarConfigurationPath, desugarImplementationPath, ouputFile, androidJarPath);
+    this.minApi = AndroidApiLevel.getAndroidApiLevel(minApi);
+  }
+
+  @Override
+  public AndroidApiLevel run() throws Exception {
+    AndroidApiLevel compilationLevel =
+        desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+    SupportedClasses supportedMethods =
+        new SupportedClassesGenerator(options, androidJar, minApi, true)
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+    System.out.println(
+        "Generating lint files for "
+            + desugaredLibrarySpecification.getIdentifier()
+            + " (compile API "
+            + compilationLevel
+            + ")");
+    writeLintFiles(compilationLevel, minApi, supportedMethods);
+    return compilationLevel;
+  }
+
+  @Override
+  Path lintFile(
+      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension) {
+    return output;
+  }
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 4 || args.length == 5) {
+      new DesugaredMethodsList(
+              parseInt(args[0]), args[1], args[2], args[3], getAndroidJarPath(args, 5))
+          .run();
+      return;
+    }
+    throw new RuntimeException(
+        StringUtils.joinLines(
+            "Invalid invocation.",
+            "Usage: DesugaredMethodList <min-api> <desugar configuration> "
+                + "<desugar implementation> <output file> [<android jar path for Android "
+                + MAX_TESTED_ANDROID_API_LEVEL
+                + " or higher>]"));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
new file mode 100644
index 0000000..16e9be8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+public class GenerateDesugaredLibraryLintFiles extends AbstractGenerateFiles {
+
+  // Only recent versions of studio support the format with fields.
+  private static final boolean FORMAT_WITH_FIELD = true;
+
+  public static GenerateDesugaredLibraryLintFiles createForTesting(
+      Path specification, Set<Path> implementation, Path outputDirectory, Path androidJar)
+      throws Exception {
+    return new GenerateDesugaredLibraryLintFiles(
+        specification, implementation, outputDirectory, androidJar);
+  }
+
+  public GenerateDesugaredLibraryLintFiles(
+      String desugarConfigurationPath,
+      String desugarImplementationPath,
+      String outputDirectory,
+      String androidJarPath)
+      throws Exception {
+    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJarPath);
+  }
+
+  GenerateDesugaredLibraryLintFiles(
+      Path desugarConfigurationPath,
+      Collection<Path> desugarImplementationPath,
+      Path outputDirectory,
+      Path androidJar)
+      throws Exception {
+    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJar);
+  }
+
+  private String lintBaseFileName(
+      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel) {
+    return "desugared_apis_" + compilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
+  }
+
+  Path lintFile(AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension)
+      throws Exception {
+    Path directory = output.resolve("compile_api_level_" + compilationApiLevel.getLevel());
+    Files.createDirectories(directory);
+    return Paths.get(
+        directory
+            + File.separator
+            + lintBaseFileName(compilationApiLevel, minApiLevel)
+            + extension);
+  }
+
+  void writeLintFiles(
+      AndroidApiLevel compilationApiLevel,
+      AndroidApiLevel minApiLevel,
+      SupportedClasses supportedClasses)
+      throws Exception {
+    // Build a plain text file with the desugared APIs.
+    List<String> desugaredApisSignatures = new ArrayList<>();
+
+    supportedClasses.forEachClass(
+        (supportedClass) -> {
+          String classBinaryName =
+              DescriptorUtils.getClassBinaryNameFromDescriptor(
+                  supportedClass.getType().descriptor.toString());
+          if (!supportedClass.getClassAnnotation().isFullySupported()) {
+            supportedClass.forEachMethodAndAnnotation(
+                (method, methodAnnotation) -> {
+                  if (method.isInstanceInitializer() || method.isClassInitializer()) {
+                    // No new constructors are added.
+                    return;
+                  }
+                  if (shouldAddMethodToLint(methodAnnotation, minApiLevel)) {
+                    desugaredApisSignatures.add(
+                        classBinaryName
+                            + '#'
+                            + method.getReference().name
+                            + method.getReference().proto.toDescriptorString());
+                  }
+                });
+            if (FORMAT_WITH_FIELD) {
+              supportedClass.forEachFieldAndAnnotation(
+                  (field, fieldAnnotation) -> {
+                    if (fieldAnnotation == null || !fieldAnnotation.unsupportedInMinApiRange) {
+                      desugaredApisSignatures.add(
+                          classBinaryName + '#' + field.getReference().name);
+                    }
+                  });
+            }
+          } else {
+            desugaredApisSignatures.add(classBinaryName);
+          }
+        });
+
+    for (DexMethod extraMethod : supportedClasses.getExtraMethods()) {
+      String classBinaryName =
+          DescriptorUtils.getClassBinaryNameFromDescriptor(
+              extraMethod.getHolderType().descriptor.toString());
+      desugaredApisSignatures.add(
+          classBinaryName + '#' + extraMethod.name + extraMethod.proto.toDescriptorString());
+    }
+
+    // Write a plain text file with the desugared APIs.
+    desugaredApisSignatures.sort(Comparator.naturalOrder());
+    FileUtils.writeTextFile(
+        lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
+  }
+
+  private boolean shouldAddMethodToLint(
+      MethodAnnotation methodAnnotation, AndroidApiLevel minApiLevel) {
+    if (methodAnnotation == null) {
+      return true;
+    }
+    if (methodAnnotation.unsupportedInMinApiRange) {
+      // Do not lint method which are unavailable with some min apis.
+      return false;
+    }
+    if (methodAnnotation.parallelStreamMethod) {
+      return minApiLevel == AndroidApiLevel.L;
+    }
+    assert methodAnnotation.missingFromLatestAndroidJar;
+    // We add missing methods from the latest jar, javac will add the squikles.
+    return true;
+  }
+
+  private void generateLintFiles(
+      AndroidApiLevel compilationApiLevel,
+      AndroidApiLevel minApiLevel,
+      SupportedClasses supportedMethods)
+      throws Exception {
+    System.out.print("  - generating for min API:");
+    System.out.print(" " + minApiLevel);
+    writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
+  }
+
+  @Override
+  public AndroidApiLevel run() throws Exception {
+    AndroidApiLevel compilationLevel =
+        desugaredLibrarySpecification.getRequiredCompilationApiLevel();
+    SupportedClasses supportedMethods =
+        new SupportedClassesGenerator(options, androidJar)
+            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
+    System.out.println(
+        "Generating lint files for "
+            + desugaredLibrarySpecification.getIdentifier()
+            + " (compile API "
+            + compilationLevel
+            + ")");
+    generateLintFiles(compilationLevel, AndroidApiLevel.B, supportedMethods);
+    generateLintFiles(compilationLevel, AndroidApiLevel.L, supportedMethods);
+    System.out.println();
+    return compilationLevel;
+  }
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 3 || args.length == 4) {
+      new GenerateDesugaredLibraryLintFiles(args[0], args[1], args[2], getAndroidJarPath(args, 4))
+          .run();
+      return;
+    }
+    throw new RuntimeException(
+        StringUtils.joinLines(
+            "Invalid invocation.",
+            "Usage: GenerateDesugaredLibraryLintFiles <desugar configuration> "
+                + "<desugar implementation> <output directory> [<android jar path for Android "
+                + MAX_TESTED_ANDROID_API_LEVEL
+                + " or higher>]"));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
index fab7228..68cad32 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -553,8 +553,7 @@
   }
 
   public AndroidApiLevel run(String outputFileName) throws Exception {
-    PrintStream ps =
-        new PrintStream(Files.newOutputStream(outputDirectory.resolve(outputFileName)));
+    PrintStream ps = new PrintStream(Files.newOutputStream(output.resolve(outputFileName)));
 
     SupportedClasses supportedClasses =
         new SupportedClassesGenerator(options, androidJar)
@@ -566,6 +565,18 @@
   }
 
   public static void main(String[] args) throws Exception {
-    AbstractGenerateFiles.main(args);
+    if (args[0].equals("--generate-api-docs")) {
+      if (args.length == 4 || args.length == 5) {
+        new GenerateHtmlDoc(args[1], args[2], args[3], getAndroidJarPath(args, 5)).run();
+        return;
+      }
+    }
+    throw new RuntimeException(
+        StringUtils.joinLines(
+            "Invalid invocation.",
+            "Usage: GenerateHtmlDoc --generate-api-docs <desugar configuration> "
+                + "<desugar implementation> <output directory> [<android jar path for Android "
+                + MAX_TESTED_ANDROID_API_LEVEL
+                + " or higher>]"));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
deleted file mode 100644
index de9f350..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar.desugaredlibrary.lint;
-
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.Version;
-import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.cf.code.CfConstNull;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.dex.Marker.Backend;
-import com.android.tools.r8.dex.Marker.Tool;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
-import com.android.tools.r8.jar.CfApplicationWriter;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.Timing;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-
-public class GenerateLintFiles extends AbstractGenerateFiles {
-
-  // Only recent versions of studio support the format with fields.
-  private static final boolean FORMAT_WITH_FIELD = true;
-
-  public static GenerateLintFiles createForTesting(
-      Path specification, Set<Path> implementation, Path outputDirectory, Path androidJar)
-      throws Exception {
-    return new GenerateLintFiles(specification, implementation, outputDirectory, androidJar);
-  }
-
-  public GenerateLintFiles(
-      String desugarConfigurationPath,
-      String desugarImplementationPath,
-      String outputDirectory,
-      String androidJarPath)
-      throws Exception {
-    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJarPath);
-  }
-
-  private GenerateLintFiles(
-      Path desugarConfigurationPath,
-      Collection<Path> desugarImplementationPath,
-      Path outputDirectory,
-      Path androidJar)
-      throws Exception {
-    super(desugarConfigurationPath, desugarImplementationPath, outputDirectory, androidJar);
-  }
-
-  private CfCode buildEmptyThrowingCfCode(DexMethod method) {
-    CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
-    return new CfCode(method.holder, 1, method.proto.parameters.size() + 1, Arrays.asList(insn));
-  }
-
-  private void addMethodsToHeaderJar(
-      DexApplication.Builder builder, DexClass clazz, Collection<DexEncodedMethod> methods) {
-    if (methods.size() == 0) {
-      return;
-    }
-
-    List<DexEncodedMethod> directMethods = new ArrayList<>();
-    List<DexEncodedMethod> virtualMethods = new ArrayList<>();
-    for (DexEncodedMethod method : methods) {
-      assert method.getHolderType() == clazz.type;
-      CfCode code = null;
-      if (!method.accessFlags.isAbstract() /*&& !method.accessFlags.isNative()*/) {
-        code = buildEmptyThrowingCfCode(method.getReference());
-      }
-      DexEncodedMethod throwingMethod =
-          DexEncodedMethod.builder()
-              .setMethod(method.getReference())
-              .setAccessFlags(method.accessFlags)
-              .setGenericSignature(MethodTypeSignature.noSignature())
-              .setCode(code)
-              .setClassFileVersion(CfVersion.V1_6)
-              .disableAndroidApiLevelCheck()
-              .build();
-      if (method.isStatic() || method.isDirectMethod()) {
-        directMethods.add(throwingMethod);
-      } else {
-        virtualMethods.add(throwingMethod);
-      }
-    }
-
-    DexEncodedMethod[] directMethodsArray = new DexEncodedMethod[directMethods.size()];
-    DexEncodedMethod[] virtualMethodsArray = new DexEncodedMethod[virtualMethods.size()];
-    directMethods.toArray(directMethodsArray);
-    virtualMethods.toArray(virtualMethodsArray);
-    assert !options.encodeChecksums;
-    ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
-    DexProgramClass programClass =
-        new DexProgramClass(
-            clazz.type,
-            null,
-            Origin.unknown(),
-            clazz.accessFlags,
-            clazz.superType,
-            clazz.interfaces,
-            null,
-            null,
-            Collections.emptyList(),
-            Collections.emptyList(),
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            ClassSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            MethodCollectionFactory.fromMethods(directMethodsArray, virtualMethodsArray),
-            false,
-            checksumSupplier);
-    builder.addProgramClass(programClass);
-  }
-
-  private String lintBaseFileName(
-      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel) {
-    return "desugared_apis_" + compilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
-  }
-
-  private Path lintFile(
-      AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension)
-      throws Exception {
-    Path directory = outputDirectory.resolve("compile_api_level_" + compilationApiLevel.getLevel());
-    Files.createDirectories(directory);
-    return Paths.get(
-        directory
-            + File.separator
-            + lintBaseFileName(compilationApiLevel, minApiLevel)
-            + extension);
-  }
-
-  private void writeLintFiles(
-      AndroidApiLevel compilationApiLevel,
-      AndroidApiLevel minApiLevel,
-      SupportedClasses supportedClasses)
-      throws Exception {
-    // Build a plain text file with the desugared APIs.
-    List<String> desugaredApisSignatures = new ArrayList<>();
-
-    LazyLoadedDexApplication.Builder builder = DexApplication.builder(options, Timing.empty());
-    supportedClasses.forEachClass(
-        (supportedClass) -> {
-          String classBinaryName =
-              DescriptorUtils.getClassBinaryNameFromDescriptor(
-                  supportedClass.getType().descriptor.toString());
-          if (!supportedClass.getClassAnnotation().isFullySupported()) {
-            supportedClass.forEachMethodAndAnnotation(
-                (method, methodAnnotation) -> {
-                  if (method.isInstanceInitializer() || method.isClassInitializer()) {
-                    // No new constructors are added.
-                    return;
-                  }
-                  if (shouldAddMethodToLint(methodAnnotation, minApiLevel)) {
-                    desugaredApisSignatures.add(
-                        classBinaryName
-                            + '#'
-                            + method.getReference().name
-                            + method.getReference().proto.toDescriptorString());
-                  }
-                });
-            if (FORMAT_WITH_FIELD) {
-              supportedClass.forEachFieldAndAnnotation(
-                  (field, fieldAnnotation) -> {
-                    if (fieldAnnotation == null || !fieldAnnotation.unsupportedInMinApiRange) {
-                      desugaredApisSignatures.add(
-                          classBinaryName + '#' + field.getReference().name);
-                    }
-                  });
-            }
-          } else {
-            desugaredApisSignatures.add(classBinaryName);
-          }
-
-          addMethodsToHeaderJar(
-              builder, supportedClass.getClazz(), supportedClass.getSupportedMethods());
-        });
-
-    // Write a plain text file with the desugared APIs.
-    desugaredApisSignatures.sort(Comparator.naturalOrder());
-    FileUtils.writeTextFile(
-        lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
-
-    // Write a header jar with the desugared APIs.
-    AppView<?> appView =
-        AppView.createForD8(
-            AppInfo.createInitialAppInfo(
-                builder.build(), GlobalSyntheticsStrategy.forNonSynthesizing()));
-    Marker marker =
-        new Marker(Tool.D8)
-            .setVersion(Version.LABEL)
-            .setCompilationMode(CompilationMode.DEBUG)
-            .setBackend(Backend.CF);
-    CfApplicationWriter writer = new CfApplicationWriter(appView, marker);
-    ClassFileConsumer consumer =
-        new ClassFileConsumer.ArchiveConsumer(
-            lintFile(compilationApiLevel, minApiLevel, FileUtils.JAR_EXTENSION));
-    writer.write(consumer);
-    consumer.finished(options.reporter);
-  }
-
-  private boolean shouldAddMethodToLint(
-      MethodAnnotation methodAnnotation, AndroidApiLevel minApiLevel) {
-    if (methodAnnotation == null) {
-      return true;
-    }
-    if (methodAnnotation.unsupportedInMinApiRange) {
-      // Do not lint method which are unavailable with some min apis.
-      return false;
-    }
-    if (methodAnnotation.parallelStreamMethod) {
-      return minApiLevel == AndroidApiLevel.L;
-    }
-    assert methodAnnotation.missingFromLatestAndroidJar;
-    // We add missing methods from the latest jar, javac will add the squikles.
-    return true;
-  }
-
-  private void generateLintFiles(
-      AndroidApiLevel compilationApiLevel,
-      AndroidApiLevel minApiLevel,
-      SupportedClasses supportedMethods)
-      throws Exception {
-    System.out.print("  - generating for min API:");
-    System.out.print(" " + minApiLevel);
-    writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
-  }
-
-  @Override
-  public AndroidApiLevel run() throws Exception {
-    AndroidApiLevel compilationLevel =
-        desugaredLibrarySpecification.getRequiredCompilationApiLevel();
-    SupportedClasses supportedMethods =
-        new SupportedClassesGenerator(options, androidJar)
-            .run(desugaredLibraryImplementation, desugaredLibrarySpecificationPath);
-    System.out.println(
-        "Generating lint files for "
-            + desugaredLibrarySpecification.getIdentifier()
-            + " (compile API "
-            + compilationLevel
-            + ")");
-    generateLintFiles(compilationLevel, AndroidApiLevel.B, supportedMethods);
-    generateLintFiles(compilationLevel, AndroidApiLevel.L, supportedMethods);
-    System.out.println();
-    return compilationLevel;
-  }
-
-  public static void main(String[] args) throws Exception {
-    AbstractGenerateFiles.main(args);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
index 307def9..ea96ecc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -31,13 +31,19 @@
  */
 public class SupportedClasses {
   private final Map<DexType, SupportedClass> supportedClasses;
+  private final List<DexMethod> extraMethods;
+
+  SupportedClasses(Map<DexType, SupportedClass> supportedClasses, List<DexMethod> extraMethods) {
+    this.supportedClasses = supportedClasses;
+    this.extraMethods = extraMethods;
+  }
 
   public void forEachClass(Consumer<SupportedClass> consumer) {
     supportedClasses.values().forEach(consumer);
   }
 
-  SupportedClasses(Map<DexType, SupportedClass> supportedClasses) {
-    this.supportedClasses = supportedClasses;
+  public List<DexMethod> getExtraMethods() {
+    return extraMethods;
   }
 
   public static class SupportedClass {
@@ -188,6 +194,7 @@
   static class Builder {
 
     Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
+    private List<DexMethod> extraMethods = ImmutableList.of();
 
     ClassAnnotation getClassAnnotation(DexType type) {
       SupportedClass.Builder builder = supportedClassBuilders.get(type);
@@ -267,13 +274,17 @@
       return classBuilder.methodAnnotations;
     }
 
+    public void setExtraMethods(List<DexMethod> extraMethods) {
+      this.extraMethods = extraMethods;
+    }
+
     SupportedClasses build() {
       Map<DexType, SupportedClass> map = new IdentityHashMap<>();
       supportedClassBuilders.forEach(
           (type, classBuilder) -> {
             map.put(type, classBuilder.build());
           });
-      return new SupportedClasses(ImmutableSortedMap.copyOf(map));
+      return new SupportedClasses(ImmutableSortedMap.copyOf(map), extraMethods);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
index f384fd7..90203a2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -47,6 +47,7 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -55,17 +56,30 @@
 
   private final InternalOptions options;
   private final DirectMappedDexApplication appForMax;
+  private final AndroidApiLevel minApi;
   private final SupportedClasses.Builder builder = SupportedClasses.builder();
+  private final boolean addBackports;
 
   public SupportedClassesGenerator(InternalOptions options, Path androidJar) throws IOException {
     this.options = options;
     this.appForMax = createAppForMax(androidJar);
+    this.minApi = AndroidApiLevel.B;
+    this.addBackports = false;
+  }
+
+  public SupportedClassesGenerator(
+      InternalOptions options, Path androidJar, AndroidApiLevel minApi, boolean addBackports)
+      throws IOException {
+    this.options = options;
+    this.appForMax = createAppForMax(androidJar);
+    this.minApi = minApi;
+    this.addBackports = addBackports;
   }
 
   public SupportedClasses run(Collection<Path> desugaredLibraryImplementation, Path specification)
       throws IOException {
     // First analyze everything which is supported when desugaring for api 1.
-    collectSupportedMembersInB(desugaredLibraryImplementation, specification);
+    collectSupportedMembersInMinApi(desugaredLibraryImplementation, specification);
     // Second annotate all apis which are partially and/or fully supported.
     annotateMethodsNotOnLatestAndroidJar();
     annotateParallelMethods();
@@ -237,13 +251,13 @@
         });
   }
 
-  private void collectSupportedMembersInB(
+  private void collectSupportedMembersInMinApi(
       Collection<Path> desugaredLibraryImplementation, Path specification) throws IOException {
 
     MachineDesugaredLibrarySpecification machineSpecification =
-        getMachineSpecification(AndroidApiLevel.B, specification);
+        getMachineSpecification(minApi, specification);
 
-    options.setMinApiLevel(AndroidApiLevel.B);
+    options.setMinApiLevel(minApi);
     options.resetDesugaredLibrarySpecificationForTesting();
     options.setDesugaredLibrarySpecification(machineSpecification);
 
@@ -304,22 +318,7 @@
 
     // All retargeted methods are supported.
     machineSpecification.forEachRetargetMethod(
-        method -> {
-          DexClass dexClass = implementationApplication.definitionFor(method.getHolderType());
-          if (dexClass != null) {
-            DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
-            if (dexEncodedMethod != null) {
-              builder.addSupportedMethod(dexClass, dexEncodedMethod);
-              builder.annotateClass(dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
-              return;
-            }
-          }
-          dexClass = appForMax.definitionFor(method.getHolderType());
-          DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
-          assert dexEncodedMethod != null;
-          builder.addSupportedMethod(dexClass, dexEncodedMethod);
-          builder.annotateClass(dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
-        });
+        method -> registerMethod(method, implementationApplication));
 
     machineSpecification
         .getStaticFieldRetarget()
@@ -341,42 +340,84 @@
               builder.addSupportedField(dexClass, dexEncodedField);
               builder.annotateClass(dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
             });
+
+    if (addBackports) {
+      List<DexMethod> extraMethods = new ArrayList<>();
+      for (DexMethod backport : backports) {
+        if (implementationApplication.definitionFor(backport.getHolderType()) == null) {
+          extraMethods.add(backport);
+        }
+      }
+      extraMethods.sort(Comparator.naturalOrder());
+      builder.setExtraMethods(extraMethods);
+    }
+  }
+
+  private void registerMethod(DexMethod method, DexApplication implementationApplication) {
+    DexClass dexClass = implementationApplication.definitionFor(method.getHolderType());
+    if (dexClass != null) {
+      DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(method);
+      if (dexEncodedMethod != null) {
+        builder.addSupportedMethod(dexClass, dexEncodedMethod);
+        builder.annotateClass(dexClass.type, ClassAnnotation.getAdditionnalMembersOnClass());
+        return;
+      }
+    }
+    dexClass = appForMax.definitionFor(method.getHolderType());
+    DexEncodedMethod dexEncodedMethod = lookupBackportMethod(dexClass, method);
+    if (dexEncodedMethod != null) {
+      builder.addSupportedMethod(dexClass, dexEncodedMethod);
+      builder.annotateClass(dexClass.getType(), ClassAnnotation.getAdditionnalMembersOnClass());
+    }
+  }
+
+  private DexEncodedMethod lookupBackportMethod(DexClass maxClass, DexMethod backport) {
+    if (maxClass == null) {
+      throw new Error(
+          "Missing class from Android "
+              + MAX_TESTED_ANDROID_API_LEVEL
+              + ": "
+              + backport.getHolderType());
+    }
+    DexEncodedMethod dexEncodedMethod = maxClass.lookupMethod(backport);
+    // Some backports are not in amendedAppForMax, such as Stream#ofNullable and recent ones
+    // introduced in U.
+    if (dexEncodedMethod == null) {
+      ImmutableSet<DexType> allStaticPublicMethods =
+          ImmutableSet.of(
+              options.dexItemFactory().mathType,
+              options.dexItemFactory().strictMathType,
+              options.dexItemFactory().objectsType);
+      if (backport
+              .toString()
+              .equals(
+                  "java.util.stream.Stream"
+                      + " java.util.stream.Stream.ofNullable(java.lang.Object)")
+          || allStaticPublicMethods.contains(backport.getHolderType())) {
+        dexEncodedMethod =
+            DexEncodedMethod.builder()
+                .setMethod(backport)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_STATIC, false))
+                .build();
+      } else {
+        throw new Error(
+            "Unexpected backport missing from Android "
+                + MAX_TESTED_ANDROID_API_LEVEL
+                + ": "
+                + backport);
+      }
+    }
+    assert dexEncodedMethod != null;
+    return dexEncodedMethod;
   }
 
   private void addBackports(DexProgramClass clazz, List<DexMethod> backports) {
     for (DexMethod backport : backports) {
       if (clazz.type == backport.getHolderType()) {
         DexClass maxClass = appForMax.definitionFor(clazz.type);
-        DexEncodedMethod dexEncodedMethod = maxClass.lookupMethod(backport);
-        // Some backports are not in amendedAppForMax, such as Stream#ofNullable and recent ones
-        // introduced in U.
-        if (dexEncodedMethod == null) {
-          ImmutableSet<DexType> allStaticPublicMethods =
-              ImmutableSet.of(
-                  options.dexItemFactory().mathType,
-                  options.dexItemFactory().strictMathType,
-                  options.dexItemFactory().objectsType);
-          if (backport
-                  .toString()
-                  .equals(
-                      "java.util.stream.Stream"
-                          + " java.util.stream.Stream.ofNullable(java.lang.Object)")
-              || allStaticPublicMethods.contains(backport.getHolderType())) {
-            dexEncodedMethod =
-                DexEncodedMethod.builder()
-                    .setMethod(backport)
-                    .setAccessFlags(
-                        MethodAccessFlags.fromSharedAccessFlags(
-                            Constants.ACC_PUBLIC | Constants.ACC_STATIC, false))
-                    .build();
-          } else {
-            throw new Error(
-                "Unexpected backport missing from Android "
-                    + MAX_TESTED_ANDROID_API_LEVEL
-                    + ": "
-                    + backport);
-          }
-        }
+        DexEncodedMethod dexEncodedMethod = lookupBackportMethod(maxClass, backport);
         builder.addSupportedMethod(clazz, dexEncodedMethod);
       }
     }
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 df5d6b1..4754658 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
@@ -736,7 +736,7 @@
             methodsDependingOnLibraryModelisation
                 .rewrittenWithLens(appView)
                 .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods()))
-        .addAll(treeFixerResult.getDispatchMethods(), appView.graphLens());
+        .addAll(treeFixerResult.getMethodsToProcess(), appView.graphLens());
     methodsDependingOnLibraryModelisation.clear();
 
     updateOptimizationInfos(executorService, feedback, treeFixerResult, previousLens);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index e12cdc8..4531523 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -60,12 +60,12 @@
       }
     }
     removeIneligibleCandidates();
+    setEnumSubclassesOnCandidates();
     removeEnumsInAnnotations();
     removePinnedCandidates();
     if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
       enumToUnboxCandidates.removeCandidate(appView.protoShrinker().references.methodToInvokeType);
     }
-    setEnumSubclassesOnCandidates();
     assert enumToUnboxCandidates.verifyAllSubtypesAreSet();
     return enumToUnboxCandidates;
   }
@@ -86,18 +86,13 @@
   }
 
   private void analyzeEnum(GraphLens graphLensForPrimaryOptimizationPass, DexProgramClass clazz) {
-    if (!appView.options().testing.enableEnumWithSubtypesUnboxing) {
-      if (legacyIsEnumUnboxingCandidate(clazz)) {
-        enumToUnboxCandidates.addCandidate(appView, clazz, graphLensForPrimaryOptimizationPass);
-      }
-      return;
-    }
     if (clazz.superType == factory.enumType) {
       if (isSuperEnumUnboxingCandidate(clazz)) {
         enumToUnboxCandidates.addCandidate(appView, clazz, graphLensForPrimaryOptimizationPass);
       }
     } else {
-      if (isSubEnumUnboxingCandidate(clazz)) {
+      if (isSubEnumUnboxingCandidate(clazz)
+          && appView.options().testing.enableEnumWithSubtypesUnboxing) {
         enumSubclasses
             .computeIfAbsent(clazz.superType, ignoreKey(Sets::newIdentityHashSet))
             .add(clazz);
@@ -135,23 +130,6 @@
     return result;
   }
 
-  private boolean legacyIsEnumUnboxingCandidate(DexProgramClass clazz) {
-    assert clazz.isEnum();
-
-    // This is used in debug mode, where we don't do quick returns to log all the reasons an enum
-    // is not unboxed.
-    boolean result = true;
-
-    if (!clazz.isEffectivelyFinal(appView)) {
-      if (!enumUnboxer.reportFailure(clazz, Reason.SUBTYPES)) {
-        return false;
-      }
-      result = false;
-    }
-
-    return isSuperEnumUnboxingCandidate(clazz) && result;
-  }
-
   private boolean isSuperEnumUnboxingCandidate(DexProgramClass clazz) {
     assert clazz.isEnum();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
index 3c2fc90..70e73dc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
@@ -67,11 +67,11 @@
   }
 
   public void removeCandidate(DexType enumType) {
-    enumTypeToInfo.remove(enumType);
+    enumTypeToInfo.remove(subEnumToSuperEnumMap.getOrDefault(enumType, enumType));
   }
 
   public boolean isCandidate(DexType enumType) {
-    return enumTypeToInfo.containsKey(enumType);
+    return enumTypeToInfo.containsKey(subEnumToSuperEnumMap.getOrDefault(enumType, enumType));
   }
 
   public boolean isEmpty() {
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 6ac7697..183243b 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
@@ -100,6 +100,7 @@
   private final EnumUnboxingUtilityClasses utilityClasses;
   private final ProgramMethodMap<CfCodeWithLens> dispatchMethods =
       ProgramMethodMap.createConcurrent();
+  private final ProgramMethodSet methodsToProcess = ProgramMethodSet.createConcurrent();
   private final PrunedItems.Builder prunedItemsBuilder;
   private final ProfileCollectionAdditions profileCollectionAdditions;
 
@@ -159,19 +160,13 @@
     BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping =
         duplicateCheckNotNullMethods(converter, executorService);
 
-    ProgramMethodSet dispatchMethodSet = ProgramMethodSet.create();
-    dispatchMethods.forEach(
-        (method, code) -> {
-          dispatchMethodSet.add(method);
-          code.setCodeLens(lens);
-        });
-
+    dispatchMethods.forEach((method, code) -> code.setCodeLens(lens));
     profileCollectionAdditions
         .setArtProfileCollection(appView.getArtProfileCollection())
         .commit(appView);
 
     return new Result(
-        checkNotNullToCheckNotZeroMapping, dispatchMethodSet, lens, prunedItemsBuilder.build());
+        checkNotNullToCheckNotZeroMapping, methodsToProcess, lens, prunedItemsBuilder.build());
   }
 
   private void cleanUpOldClass(DexProgramClass clazz) {
@@ -657,6 +652,8 @@
       ProgramMethodSet unorderedSubimplementations) {
     assert !unorderedSubimplementations.isEmpty();
     DexMethod superUtilityMethod;
+    List<ProgramMethod> sortedSubimplementations = new ArrayList<>(unorderedSubimplementations);
+    sortedSubimplementations.sort(Comparator.comparing(ProgramMethod::getHolderType));
     if (superMethod.isProgramMethod()) {
       superUtilityMethod =
           installLocalUtilityMethod(
@@ -664,12 +661,17 @@
     } else {
       // All methods but toString() are final or non-virtual.
       // We could support other cases by setting correctly the superUtilityMethod here.
-      assert superMethod.getReference() == factory.enumMembers.toString;
-      superUtilityMethod = localUtilityClass.computeToStringUtilityMethod(factory);
+      assert superMethod.getReference().match(factory.enumMembers.toString);
+      ProgramMethod toString = localUtilityClass.ensureToStringMethod(appView);
+      superUtilityMethod = toString.getReference();
+      for (ProgramMethod context : sortedSubimplementations) {
+        // If the utility method is used only from the dispatch method, we have to process it and
+        // add it to the ArtProfile.
+        methodsToProcess.add(toString);
+        profileCollectionAdditions.addMethodIfContextIsInProfile(toString, context);
+      }
     }
     Map<DexMethod, DexMethod> overrideToUtilityMethods = new IdentityHashMap<>();
-    List<ProgramMethod> sortedSubimplementations = new ArrayList<>(unorderedSubimplementations);
-    sortedSubimplementations.sort(Comparator.comparing(ProgramMethod::getHolderType));
     for (ProgramMethod subMethod : sortedSubimplementations) {
       DexMethod subEnumLocalUtilityMethod =
           installLocalUtilityMethod(localUtilityClass, localUtilityMethods, subMethod);
@@ -769,6 +771,7 @@
     ProgramMethod dispatchMethod =
         newLocalUtilityMethod.asProgramMethod(localUtilityClass.getDefinition());
     dispatchMethods.put(dispatchMethod, codeWithLens);
+    methodsToProcess.add(dispatchMethod);
     for (ProgramMethod context : contexts) {
       profileCollectionAdditions.addMethodIfContextIsInProfile(dispatchMethod, context);
     }
@@ -923,17 +926,17 @@
   public static class Result {
 
     private final BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping;
-    private final ProgramMethodSet dispatchMethods;
+    private final ProgramMethodSet methodsToProcess;
     private final EnumUnboxingLens lens;
     private final PrunedItems prunedItems;
 
     Result(
         BiMap<DexMethod, DexMethod> checkNotNullToCheckNotZeroMapping,
-        ProgramMethodSet dispatchMethods,
+        ProgramMethodSet methodsToProcess,
         EnumUnboxingLens lens,
         PrunedItems prunedItems) {
       this.checkNotNullToCheckNotZeroMapping = checkNotNullToCheckNotZeroMapping;
-      this.dispatchMethods = dispatchMethods;
+      this.methodsToProcess = methodsToProcess;
       this.lens = lens;
       this.prunedItems = prunedItems;
     }
@@ -942,8 +945,8 @@
       return checkNotNullToCheckNotZeroMapping;
     }
 
-    public ProgramMethodSet getDispatchMethods() {
-      return dispatchMethods;
+    public ProgramMethodSet getMethodsToProcess() {
+      return methodsToProcess;
     }
 
     EnumUnboxingLens getLens() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
index a7fdb84..5ca2e28 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
@@ -82,11 +81,8 @@
     return factory.createProto(field.getType(), factory.intType);
   }
 
-  public DexMethod computeToStringUtilityMethod(DexItemFactory factory) {
-    DexField nameField = factory.enumMembers.nameField;
-    DexString name = computeGetInstanceFieldMethodName(nameField, factory);
-    DexProto proto = computeGetInstanceFieldMethodProto(nameField, factory);
-    return factory.createMethod(getDefinition().getType(), proto, name);
+  public ProgramMethod ensureToStringMethod(AppView<AppInfoWithLiveness> appView) {
+    return ensureGetInstanceFieldMethod(appView, appView.dexItemFactory().enumMembers.nameField);
   }
 
   private ProgramMethod ensureGetInstanceFieldMethod(
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 6dcfe40..bda771a 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -35,8 +35,10 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeNewArray;
@@ -67,9 +69,12 @@
 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.LirBuilder.IntSwitchPayload;
 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.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -476,6 +481,39 @@
     }
 
     @Override
+    public void onIntSwitch(EV value, IntSwitchPayload payload) {
+      // keys is the 'value' -> 'target index' mapping.
+      int[] keys = payload.keys;
+      // successorIndices is the 'target index' to 'IR successor index'.
+      int[] successorIndices = new int[keys.length];
+      List<BasicBlock> successorBlocks = new ArrayList<>();
+      {
+        // The mapping from instruction to successor is a temp mapping to track if any targets
+        // point to the same block.
+        Int2IntMap instructionToSuccessor = new Int2IntOpenHashMap();
+        for (int i = 0; i < successorIndices.length; i++) {
+          int instructionIndex = payload.targets[i];
+          if (instructionToSuccessor.containsKey(instructionIndex)) {
+            successorIndices[i] = instructionToSuccessor.get(instructionIndex);
+          } else {
+            int successorIndex = successorBlocks.size();
+            successorIndices[i] = successorIndex;
+            instructionToSuccessor.put(instructionIndex, successorIndex);
+            successorBlocks.add(getBasicBlock(instructionIndex));
+          }
+        }
+      }
+      int fallthrough = successorBlocks.size();
+      addInstruction(new IntSwitch(getValue(value), keys, successorIndices, fallthrough));
+      // The call to addInstruction will ensure the current block so don't amend to it before here.
+      // If the block has successors then the index mappings are not valid / need to be offset.
+      assert currentBlock.getSuccessors().isEmpty();
+      successorBlocks.forEach(currentBlock::link);
+      currentBlock.link(getBasicBlock(nextInstructionIndex));
+      closeCurrentBlock();
+    }
+
+    @Override
     public void onInvokeDirect(DexMethod target, List<EV> arguments, boolean isInterface) {
       Value dest = getInvokeInstructionOutputValue(target);
       List<Value> ssaArgumentValues = getValues(arguments);
@@ -588,6 +626,12 @@
     }
 
     @Override
+    public void onInstanceOf(DexType type, EV value) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      addInstruction(new InstanceOf(dest, getValue(value), type));
+    }
+
+    @Override
     public void onDebugPosition() {
       addInstruction(new DebugPosition());
     }
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 1239c11..7a88ae2 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -37,6 +37,7 @@
 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.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
@@ -83,12 +84,25 @@
   private int[] valueIndexBuffer = new int[MAX_VALUE_COUNT];
 
   /**
+   * Internal "DexItem" for the instruction payloads such that they can be put in the pool.
+   *
+   * <p>The instruction encoding assumes the instruction operand payload size is u1, so this allows
+   * the data payload to be stored in the constant pool instead.
+   */
+  public abstract static class InstructionPayload extends DexItem {
+    @Override
+    protected final void collectMixedSectionItems(MixedSectionCollection collection) {
+      throw new Unreachable();
+    }
+  }
+
+  /**
    * Internal "DexItem" for the fill-array payloads such that they can be put in the pool.
    *
    * <p>The instruction encoding assumes the instruction operand payload size is u1, so the data
    * payload is stored in the constant pool instead.
    */
-  public static class FillArrayPayload extends DexItem {
+  public static class FillArrayPayload extends InstructionPayload {
     public final int element_width;
     public final long size;
     public final short[] data;
@@ -98,10 +112,16 @@
       this.size = size;
       this.data = data;
     }
+  }
 
-    @Override
-    protected void collectMixedSectionItems(MixedSectionCollection collection) {
-      throw new Unreachable();
+  public static class IntSwitchPayload extends InstructionPayload {
+    public final int[] keys;
+    public final int[] targets;
+
+    public IntSwitchPayload(int[] keys, int[] targets) {
+      assert keys.length == targets.length;
+      this.keys = keys;
+      this.targets = targets;
     }
   }
 
@@ -401,6 +421,11 @@
         LirOpcodes.CHECKCAST, Collections.singletonList(type), Collections.singletonList(value));
   }
 
+  public LirBuilder<V, EV> addInstanceOf(DexType type, V value) {
+    return addInstructionTemplate(
+        LirOpcodes.INSTANCEOF, Collections.singletonList(type), Collections.singletonList(value));
+  }
+
   public LirBuilder<V, EV> addStaticGet(DexField field) {
     return addOneItemInstruction(LirOpcodes.GETSTATIC, field);
   }
@@ -484,6 +509,22 @@
     return this;
   }
 
+  public LirBuilder<V, EV> addIntSwitch(
+      V value,
+      int[] keys,
+      Int2ReferenceSortedMap<BasicBlock> keyToTargetMap,
+      BasicBlock fallthroughBlock) {
+    int[] targets = new int[keys.length];
+    for (int i = 0; i < keys.length; i++) {
+      targets[i] = getBlockIndex(keyToTargetMap.get(keys[i]));
+    }
+    IntSwitchPayload payload = new IntSwitchPayload(keys, targets);
+    return addInstructionTemplate(
+        LirOpcodes.TABLESWITCH,
+        Collections.singletonList(payload),
+        Collections.singletonList(value));
+  }
+
   public LirBuilder<V, EV> addIf(
       IfType ifKind, ValueType valueType, V value, BasicBlock trueTarget) {
     int opcode;
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 8d81728..d418d96 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.lightir.LirBuilder.FillArrayPayload;
+import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -288,6 +289,10 @@
     onInstruction();
   }
 
+  public void onIntSwitch(EV value, IntSwitchPayload payload) {
+    onInstruction();
+  }
+
   public void onFallthrough() {
     onInstruction();
   }
@@ -372,6 +377,10 @@
     onInstruction();
   }
 
+  public void onInstanceOf(DexType type, EV value) {
+    onInstruction();
+  }
+
   public void onDebugPosition() {
     onInstruction();
   }
@@ -827,6 +836,14 @@
           onGoto(blockIndex);
           return;
         }
+      case LirOpcodes.TABLESWITCH:
+        {
+          IntSwitchPayload payload =
+              (IntSwitchPayload) getConstantItem(view.getNextConstantOperand());
+          EV value = getNextValueOperand(view);
+          onIntSwitch(value, payload);
+          return;
+        }
       case LirOpcodes.INVOKEDIRECT:
       case LirOpcodes.INVOKEDIRECT_ITF:
         {
@@ -938,6 +955,13 @@
           onCheckCast(type, value);
           return;
         }
+      case LirOpcodes.INSTANCEOF:
+        {
+          DexType type = getNextDexTypeOperand(view);
+          EV value = getNextValueOperand(view);
+          onInstanceOf(type, value);
+          return;
+        }
       case LirOpcodes.DEBUGPOS:
         {
           onDebugPosition();
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 58ac9b2..e19661e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -11,6 +11,7 @@
 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.lightir.LirBuilder.IntSwitchPayload;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
 import java.util.List;
@@ -202,6 +203,12 @@
   }
 
   @Override
+  public void onIntSwitch(EV value, IntSwitchPayload payload) {
+    appendValueArguments(value);
+    // TODO(b/225838009): Consider printing the switch payload info.
+  }
+
+  @Override
   public void onFallthrough() {
     // Nothing to append.
   }
@@ -304,6 +311,13 @@
   }
 
   @Override
+  public void onInstanceOf(DexType type, EV value) {
+    appendOutValue();
+    appendValueArguments(value);
+    builder.append(type);
+  }
+
+  @Override
   public void onArrayGetPrimitive(MemberType type, EV array, EV index) {
     appendOutValue();
     appendValueArguments(array, index);
diff --git a/src/main/java/com/android/tools/r8/retrace/FinishedPartitionMappingCallback.java b/src/main/java/com/android/tools/r8/retrace/FinishedPartitionMappingCallback.java
new file mode 100644
index 0000000..6c4c906
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/FinishedPartitionMappingCallback.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.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+
+/***
+ * Interface for registering a callback when a retracing operation is finished.
+ */
+@FunctionalInterface
+@Keep
+public interface FinishedPartitionMappingCallback {
+
+  FinishedPartitionMappingCallback EMPTY_INSTANCE = diagnosticsHandler -> {};
+
+  static FinishedPartitionMappingCallback empty() {
+    return EMPTY_INSTANCE;
+  }
+
+  void finished(DiagnosticsHandler handler);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingSupplierBase.java b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBase.java
index accecec..53ed349 100644
--- a/src/main/java/com/android/tools/r8/retrace/MappingSupplierBase.java
+++ b/src/main/java/com/android/tools/r8/retrace/MappingSupplierBase.java
@@ -5,13 +5,14 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Finishable;
 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>> {
+public interface MappingSupplierBase<T extends MappingSupplierBase<T>> extends Finishable {
 
   /***
    * Register an allowed mapping lookup to allow for prefetching of resources.
diff --git a/src/main/java/com/android/tools/r8/retrace/Partition.java b/src/main/java/com/android/tools/r8/retrace/Partition.java
new file mode 100644
index 0000000..c7b942f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/Partition.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import static com.android.tools.r8.utils.ExceptionUtils.failWithFakeEntry;
+import static com.android.tools.r8.utils.ExceptionUtils.withMainProgramHandler;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.ParseFlagInfo;
+import com.android.tools.r8.ParseFlagInfoImpl;
+import com.android.tools.r8.ParseFlagPrinter;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.utils.OptionsParsing;
+import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import com.android.tools.r8.utils.PartitionMapZipContainer;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+/** A tool for creating a partition-map from a proguard map. */
+@Keep
+public class Partition {
+
+  private static final String USAGE_MESSAGE =
+      StringUtils.lines(
+          "Usage: partition [options] <proguard-map>"
+              + "where <proguard-map> is a generated mapping file and options are:");
+
+  public static List<ParseFlagInfo> getFlags() {
+    return ImmutableList.<ParseFlagInfo>builder()
+        .add(
+            ParseFlagInfoImpl.flag1(
+                "--output", "<partition-map>", "Output destination of partitioned map"))
+        .add(ParseFlagInfoImpl.getHelp())
+        .build();
+  }
+
+  static String getUsageMessage() {
+    StringBuilder builder = new StringBuilder();
+    StringUtils.appendLines(builder, USAGE_MESSAGE);
+    new ParseFlagPrinter().addFlags(getFlags()).appendLinesToBuilder(builder);
+    return builder.toString();
+  }
+
+  private static PartitionCommand.Builder parseArguments(
+      String[] args, DiagnosticsHandler diagnosticsHandler) {
+    ParseContext context = new ParseContext(args);
+    PartitionCommand.Builder builder = PartitionCommand.builder();
+    boolean hasSetProguardMap = false;
+    while (context.head() != null) {
+      Boolean help = OptionsParsing.tryParseBoolean(context, "--help");
+      if (help != null) {
+        return null;
+      }
+      String output = OptionsParsing.tryParseSingle(context, "--output", null);
+      if (output != null && !output.isEmpty()) {
+        builder.setPartitionMapConsumer(
+            PartitionMapZipContainer.createPartitionMapZipContainerConsumer(Paths.get(output)));
+        continue;
+      }
+      if (!hasSetProguardMap) {
+        builder.setProguardMapProducer(ProguardMapProducer.fromPath(Paths.get(context.head())));
+        context.next();
+        hasSetProguardMap = true;
+      } else {
+        diagnosticsHandler.error(new StringDiagnostic(getUsageMessage()));
+        throw new RetracePartitionException(
+            String.format("Too many arguments specified for builder at '%s'", context.head()));
+      }
+    }
+    return builder;
+  }
+
+  public static void run(String[] args) throws RetracePartitionException {
+    run(args, new DiagnosticsHandler() {});
+  }
+
+  private static void run(String[] args, DiagnosticsHandler diagnosticsHandler) {
+    PartitionCommand.Builder builder = parseArguments(args, diagnosticsHandler);
+    if (builder == null) {
+      assert Arrays.asList(args).contains("--help");
+      System.out.println("Partition " + Version.getVersionString());
+      System.out.print(getUsageMessage());
+      return;
+    }
+    run(builder.build());
+  }
+
+  /**
+   * The main entry point for partitioning a map.
+   *
+   * @param command The command that describes the desired behavior of this partition invocation.
+   */
+  public static void run(PartitionCommand command) {
+    try {
+      command
+          .getPartitionMapConsumer()
+          .acceptMappingPartitionMetadata(
+              ProguardMapPartitioner.builder(command.getDiagnosticsHandler())
+                  .setProguardMapProducer(command.getProguardMapProducer())
+                  .setPartitionConsumer(command.getPartitionMapConsumer()::acceptMappingPartition)
+                  .setAllowEmptyMappedRanges(true)
+                  .setAllowExperimentalMapping(false)
+                  .build()
+                  .run());
+      command.getPartitionMapConsumer().finished(command.getDiagnosticsHandler());
+    } catch (Throwable t) {
+      throw failWithFakeEntry(
+          command.getDiagnosticsHandler(),
+          t,
+          (message, cause, ignore) -> new RetracePartitionException(message, cause),
+          RetracePartitionException.class);
+    }
+  }
+
+  /**
+   * The main entry point for running a legacy proguard map to partition map from command line.
+   *
+   * @param args The argument that describes this command.
+   */
+  public static void main(String... args) {
+    withMainProgramHandler(() -> run(args));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionCommand.java b/src/main/java/com/android/tools/r8/retrace/PartitionCommand.java
new file mode 100644
index 0000000..3c5db33
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionCommand.java
@@ -0,0 +1,75 @@
+// 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.PartitionMapConsumer;
+
+@Keep
+public class PartitionCommand {
+
+  private final DiagnosticsHandler diagnosticsHandler;
+  private final ProguardMapProducer proguardMapProducer;
+  private final PartitionMapConsumer partitionMapConsumer;
+
+  private PartitionCommand(
+      DiagnosticsHandler diagnosticsHandler,
+      ProguardMapProducer proguardMapProducer,
+      PartitionMapConsumer partitionMapConsumer) {
+    this.diagnosticsHandler = diagnosticsHandler;
+    this.proguardMapProducer = proguardMapProducer;
+    this.partitionMapConsumer = partitionMapConsumer;
+  }
+
+  public DiagnosticsHandler getDiagnosticsHandler() {
+    return diagnosticsHandler;
+  }
+
+  public ProguardMapProducer getProguardMapProducer() {
+    return proguardMapProducer;
+  }
+
+  public PartitionMapConsumer getPartitionMapConsumer() {
+    return partitionMapConsumer;
+  }
+
+  /** Utility method for obtaining a RetraceCommand builder with a default diagnostics handler. */
+  public static Builder builder() {
+    return new Builder(new DiagnosticsHandler() {});
+  }
+
+  @Keep
+  public static class Builder {
+
+    private final DiagnosticsHandler diagnosticsHandler;
+    private ProguardMapProducer proguardMapProducer;
+    private PartitionMapConsumer partitionMapConsumer;
+
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+    }
+
+    public Builder setProguardMapProducer(ProguardMapProducer proguardMapProducer) {
+      this.proguardMapProducer = proguardMapProducer;
+      return this;
+    }
+
+    public Builder setPartitionMapConsumer(PartitionMapConsumer partitionMapConsumer) {
+      this.partitionMapConsumer = partitionMapConsumer;
+      return this;
+    }
+
+    public PartitionCommand build() {
+      if (proguardMapProducer == null) {
+        throw new RetracePartitionException("ProguardMapSupplier not specified");
+      }
+      if (partitionMapConsumer == null) {
+        throw new RetracePartitionException("PartitionMapConsumer not specified");
+      }
+      return new PartitionCommand(diagnosticsHandler, proguardMapProducer, partitionMapConsumer);
+    }
+  }
+}
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 ebcfaf9..d89ce17 100644
--- a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplier.java
@@ -22,10 +22,17 @@
       RegisterMappingPartitionCallback registerCallback,
       PrepareMappingPartitionsCallback prepareCallback,
       MappingPartitionFromKeySupplier partitionSupplier,
+      FinishedPartitionMappingCallback finishedCallback,
       boolean allowExperimental,
       byte[] metadata,
       MapVersion fallbackMapVersion) {
-    super(registerCallback, prepareCallback, allowExperimental, metadata, fallbackMapVersion);
+    super(
+        registerCallback,
+        prepareCallback,
+        finishedCallback,
+        allowExperimental,
+        metadata,
+        fallbackMapVersion);
     this.partitionSupplier = partitionSupplier;
   }
 
@@ -70,6 +77,15 @@
     return createRetracerFromPartitionSupplier(diagnosticsHandler, partitionSupplier);
   }
 
+  public MappingPartitionFromKeySupplier getMappingPartitionFromKeySupplier() {
+    return partitionSupplier;
+  }
+
+  @Override
+  public PartitionMappingSupplier getPartitionMappingSupplier() {
+    return this;
+  }
+
   @Override
   public PartitionMappingSupplier self() {
     return this;
@@ -124,6 +140,7 @@
           registerCallback,
           prepareCallback,
           partitionSupplier,
+          finishedCallback,
           allowExperimental,
           null,
           fallbackMapVersion);
@@ -165,6 +182,7 @@
           registerCallback,
           prepareCallback,
           partitionSupplier,
+          finishedCallback,
           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
index 41acee5..9e0f71b 100644
--- a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierAsync.java
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierAsync.java
@@ -21,10 +21,17 @@
   private PartitionMappingSupplierAsync(
       RegisterMappingPartitionCallback registerCallback,
       PrepareMappingPartitionsCallback prepareCallback,
+      FinishedPartitionMappingCallback finishedCallback,
       boolean allowExperimental,
       byte[] metadata,
       MapVersion fallbackMapVersion) {
-    super(registerCallback, prepareCallback, allowExperimental, metadata, fallbackMapVersion);
+    super(
+        registerCallback,
+        prepareCallback,
+        finishedCallback,
+        allowExperimental,
+        metadata,
+        fallbackMapVersion);
   }
 
   /***
@@ -107,7 +114,12 @@
         throw new RuntimeException("Cannot build without providing metadata.");
       }
       return new PartitionMappingSupplierAsync(
-          registerCallback, prepareCallback, allowExperimental, metadata, fallbackMapVersion);
+          registerCallback,
+          prepareCallback,
+          finishedCallback,
+          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
index f5b020b..1dc2e14 100644
--- a/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionMappingSupplierBuilderBase.java
@@ -15,6 +15,8 @@
       RegisterMappingPartitionCallback.empty();
   protected PrepareMappingPartitionsCallback prepareCallback =
       PrepareMappingPartitionsCallback.empty();
+  protected FinishedPartitionMappingCallback finishedCallback =
+      FinishedPartitionMappingCallback.empty();
   protected final MapVersion fallbackMapVersion;
   protected boolean allowExperimental = false;
 
@@ -27,6 +29,11 @@
     return self();
   }
 
+  public T setFinishedPartitionMappingCallback(FinishedPartitionMappingCallback finishedCallback) {
+    this.finishedCallback = finishedCallback;
+    return self();
+  }
+
   public T setPrepareMappingPartitionsCallback(PrepareMappingPartitionsCallback prepareCallback) {
     this.prepareCallback = prepareCallback;
     return self();
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
index 4a4d593..d55881cf 100644
--- a/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
@@ -9,12 +9,11 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Finishable;
 import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.dex.CompatByteBuffer;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.LineReader;
-import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal;
 import com.android.tools.r8.retrace.internal.MetadataAdditionalInfo;
+import com.android.tools.r8.retrace.internal.PartitionMappingSupplierBase;
 import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import java.io.ByteArrayInputStream;
@@ -23,30 +22,37 @@
 public class PartitionedToProguardMappingConverter {
 
   private final StringConsumer consumer;
-  private final MappingPartitionFromKeySupplier partitionSupplier;
-  private final byte[] metadata;
+  private final PartitionMappingSupplierBase<?> partitionMappingSupplier;
   private final DiagnosticsHandler diagnosticsHandler;
 
   private PartitionedToProguardMappingConverter(
       StringConsumer consumer,
-      MappingPartitionFromKeySupplier partitionSupplier,
-      byte[] metadata,
+      PartitionMappingSupplierBase<?> partitionMappingSupplier,
       DiagnosticsHandler diagnosticsHandler) {
     this.consumer = consumer;
-    this.partitionSupplier = partitionSupplier;
-    this.metadata = metadata;
+    this.partitionMappingSupplier = partitionMappingSupplier;
     this.diagnosticsHandler = diagnosticsHandler;
   }
 
-  public void run() throws RetracePartitionException {
+  private MappingPartitionMetadataInternal getMetadata() {
     MappingPartitionMetadataInternal metadataInternal =
-        MappingPartitionMetadataInternal.deserialize(
-            CompatByteBuffer.wrapOrNull(metadata),
-            MapVersion.MAP_VERSION_UNKNOWN,
-            diagnosticsHandler);
-    if (!metadataInternal.canGetPartitionKeys()) {
+        partitionMappingSupplier.getMetadata(diagnosticsHandler);
+    if (metadataInternal == null || !metadataInternal.canGetPartitionKeys()) {
       throw new RetracePartitionException("Cannot obtain all partition keys from metadata");
     }
+    return metadataInternal;
+  }
+
+  private void requestKeys(MappingPartitionMetadataInternal metadataInternal) {
+    for (String partitionKey : metadataInternal.getPartitionKeys()) {
+      partitionMappingSupplier.registerKeyUse(partitionKey);
+    }
+  }
+
+  private void run(
+      MappingPartitionMetadataInternal metadataInternal,
+      MappingPartitionFromKeySupplier partitionSupplier)
+      throws RetracePartitionException {
     ProguardMapWriter consumer = new ProguardMapWriter(this.consumer, diagnosticsHandler);
     if (metadataInternal.canGetAdditionalInfo()) {
       MetadataAdditionalInfo additionalInfo = metadataInternal.getAdditionalInfo();
@@ -72,6 +78,25 @@
       }
     }
     consumer.finished(diagnosticsHandler);
+    partitionMappingSupplier.finished(diagnosticsHandler);
+  }
+
+  public void run() throws RetracePartitionException {
+    MappingPartitionMetadataInternal metadata = getMetadata();
+    PartitionMappingSupplier syncSupplier = partitionMappingSupplier.getPartitionMappingSupplier();
+    if (syncSupplier == null) {
+      throw new RetracePartitionException(
+          "Running synchronously requires a synchronous partition mapping provider. Use runAsync()"
+              + " if you have an asynchronous provider.");
+    }
+    requestKeys(metadata);
+    run(metadata, syncSupplier.getMappingPartitionFromKeySupplier());
+  }
+
+  public RetraceAsyncAction runAsync() throws RetracePartitionException {
+    MappingPartitionMetadataInternal metadata = getMetadata();
+    requestKeys(metadata);
+    return supplier -> run(metadata, supplier);
   }
 
   private static class ProguardMapWriter implements ChainableStringConsumer, Finishable {
@@ -103,8 +128,7 @@
   public static class Builder {
 
     private StringConsumer consumer;
-    private MappingPartitionFromKeySupplier partitionSupplier;
-    private byte[] metadata;
+    private PartitionMappingSupplierBase<?> partitionSupplier;
     private DiagnosticsHandler diagnosticsHandler;
 
     public Builder setConsumer(StringConsumer consumer) {
@@ -112,16 +136,11 @@
       return this;
     }
 
-    public Builder setPartitionSupplier(MappingPartitionFromKeySupplier partitionSupplier) {
+    public Builder setPartitionMappingSupplier(PartitionMappingSupplierBase<?> partitionSupplier) {
       this.partitionSupplier = partitionSupplier;
       return this;
     }
 
-    public Builder setMetadata(byte[] metadata) {
-      this.metadata = metadata;
-      return this;
-    }
-
     public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
       this.diagnosticsHandler = diagnosticsHandler;
       return this;
@@ -129,7 +148,7 @@
 
     public PartitionedToProguardMappingConverter build() {
       return new PartitionedToProguardMappingConverter(
-          consumer, partitionSupplier, metadata, diagnosticsHandler);
+          consumer, partitionSupplier, diagnosticsHandler);
     }
   }
 }
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 0150f83..4646f16 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import com.android.tools.r8.utils.PartitionMapZipContainer;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
@@ -60,6 +61,7 @@
         .add(ParseFlagInfoImpl.flag0("--info", "Write information messages to stdout"))
         .add(ParseFlagInfoImpl.flag0("--quiet", "Silence ordinary messages printed to stdout"))
         .add(ParseFlagInfoImpl.flag0("--verify-mapping-file-hash", "Verify the mapping file hash"))
+        .add(ParseFlagInfoImpl.getHelp())
         .build();
   }
 
@@ -101,7 +103,7 @@
         hasSetQuiet = true;
         continue;
       }
-      String regex = OptionsParsing.tryParseSingle(context, "--regex", "r");
+      String regex = OptionsParsing.tryParseSingle(context, "--regex", "--r");
       if (regex != null && !regex.isEmpty()) {
         builder.setRegularExpression(regex);
         continue;
@@ -112,6 +114,12 @@
         hasSetStackTrace = true;
         continue;
       }
+      String partitionMap = OptionsParsing.tryParseSingle(context, "--partition-map", "--p");
+      if (partitionMap != null && !partitionMap.isEmpty()) {
+        builder.setMappingSupplier(getPartitionMappingSupplier(partitionMap, diagnosticsHandler));
+        hasSetProguardMap = true;
+        continue;
+      }
       if (!hasSetProguardMap) {
         builder.setMappingSupplier(getMappingSupplier(context.head(), diagnosticsHandler));
         context.next();
@@ -138,6 +146,22 @@
     return builder;
   }
 
+  private static MappingSupplier<?> getPartitionMappingSupplier(
+      String partitionMap, DiagnosticsHandler diagnosticsHandler) {
+    Path path = Paths.get(partitionMap);
+    if (!Files.exists(path)) {
+      diagnosticsHandler.error(
+          new StringDiagnostic(String.format("Could not find mapping file '%s'.", partitionMap)));
+      throw new RetraceAbortException();
+    }
+    try {
+      return PartitionMapZipContainer.createPartitionMapZipContainerSupplier(path);
+    } catch (Exception e) {
+      diagnosticsHandler.error(new ExceptionDiagnostic(e));
+      throw new RetraceAbortException();
+    }
+  }
+
   private static ProguardMappingSupplier getMappingSupplier(
       String mappingPath, DiagnosticsHandler diagnosticsHandler) {
     Path path = Paths.get(mappingPath);
@@ -297,6 +321,7 @@
                       RetraceUnknownMapVersionDiagnostic.create(mapVersionInfo.getValue()));
                 }
               });
+      mappingSupplier.finished(diagnosticsHandler);
     } catch (InvalidMappingFileException e) {
       command.getOptions().getDiagnosticsHandler().error(new ExceptionDiagnostic(e));
       throw e;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceAsyncAction.java b/src/main/java/com/android/tools/r8/retrace/RetraceAsyncAction.java
new file mode 100644
index 0000000..a6088e8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceAsyncAction.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 RetraceAsyncAction {
+
+  void execute(MappingPartitionFromKeySupplier supplier);
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracePartitionException.java b/src/main/java/com/android/tools/r8/retrace/RetracePartitionException.java
index 5712f0d..d441e22 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracePartitionException.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracePartitionException.java
@@ -16,4 +16,8 @@
   public RetracePartitionException(Exception e) {
     super(e);
   }
+
+  public RetracePartitionException(String message, Throwable cause) {
+    super(message, cause);
+  }
 }
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 2c16ea1..df888dc 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -4,14 +4,13 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
-
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.retrace.internal.RetraceStackFrameResultWithContextImpl;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -78,24 +77,9 @@
    */
   public RetraceStackFrameResultWithContext<String> retrace(
       List<String> stackTrace, RetraceStackTraceContext context) {
-    RetraceStackTraceResult<String> listRetraceStackTraceResult =
-        retraceStackTrace(stackTrace, context);
-    List<String> retracedStrings = new ArrayList<>();
-    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);
-                    }
-                  }
-                }));
+    RetraceStackTraceResult<String> result = retraceStackTrace(stackTrace, context);
     return RetraceStackFrameResultWithContextImpl.create(
-        retracedStrings, listRetraceStackTraceResult.getContext());
+        joinAmbiguousLines(result.getResult()), result.getContext());
   }
 
   /**
@@ -108,38 +92,9 @@
    */
   public RetraceStackFrameResultWithContext<String> retraceParsed(
       List<StackTraceElementStringProxy> stackTrace, RetraceStackTraceContext context) {
-    RetraceStackTraceResult<String> listRetraceStackTraceResult =
-        retraceStackTraceParsed(stackTrace, context);
-    List<String> retracedStrings = new ArrayList<>();
-    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);
-                    }
-                  }
-                }));
+    RetraceStackTraceResult<String> result = retraceStackTraceParsed(stackTrace, context);
     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);
-    }
-    retracedStrings.add(
-        stackTraceLine.substring(0, indexToInsertOr)
-            + "<OR> "
-            + stackTraceLine.substring(indexToInsertOr));
+        joinAmbiguousLines(result.getResult()), result.getContext());
   }
 
   /**
@@ -153,10 +108,9 @@
       String stackTraceLine, RetraceStackTraceContext context) {
     RetraceStackFrameAmbiguousResultWithContext<String> listRetraceStackTraceResult =
         retraceFrame(stackTraceLine, context);
-    List<String> result = new ArrayList<>();
-    joinAmbiguousLines(listRetraceStackTraceResult.getAmbiguousResult(), result::add);
     return RetraceStackFrameResultWithContextImpl.create(
-        result, listRetraceStackTraceResult.getContext());
+        joinAmbiguousLines(Collections.singletonList(listRetraceStackTraceResult)),
+        listRetraceStackTraceResult.getContext());
   }
 
   /**
@@ -176,32 +130,42 @@
     }
   }
 
-  private void joinAmbiguousLines(
-      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<>();
+  private List<String> joinAmbiguousLines(
+      List<RetraceStackFrameAmbiguousResult<String>> retracedResult) {
+    List<String> result = new ArrayList<>();
     retracedResult.forEach(
         potentialResults -> {
-          assert !potentialResults.isEmpty();
-          // Check if we already reported position.
-          if (reportedFrames.add(potentialResults.get(0))) {
-            boolean isAmbiguous = potentialResults != retracedResult.get(0);
-            potentialResults.forEach(
-                retracedString -> {
-                  if (isAmbiguous) {
-                    int firstCharIndex = firstNonWhiteSpaceCharacterFromIndex(retracedString, 0);
-                    joinedConsumer.accept(
-                        retracedString.substring(0, firstCharIndex)
-                            + "<OR> "
-                            + retracedString.substring(firstCharIndex));
-                  } else {
-                    joinedConsumer.accept(retracedString);
-                  }
-                });
-          }
+          Set<String> reportedFrames = new HashSet<>();
+          potentialResults.forEachWithIndex(
+              (inlineFrames, index) -> {
+                // Check if we already reported position.
+                String topFrame = inlineFrames.get(0);
+                if (reportedFrames.add(topFrame)) {
+                  inlineFrames.forEach(
+                      inlineFrame -> {
+                        boolean isAmbiguous = index > 0 && topFrame.equals(inlineFrame);
+                        if (isAmbiguous) {
+                          result.add(insertOrIntoStackTraceLine(inlineFrame));
+                        } else {
+                          result.add(inlineFrame);
+                        }
+                      });
+                }
+              });
         });
+    return result;
+  }
+
+  private String insertOrIntoStackTraceLine(String stackTraceLine) {
+    // 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 stackTraceLine.substring(0, indexToInsertOr)
+        + "<OR> "
+        + stackTraceLine.substring(indexToInsertOr);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
index 5a9fc41..012fb94 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
@@ -7,6 +7,7 @@
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Finishable;
 import com.android.tools.r8.dex.CompatByteBuffer;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.LineReader;
@@ -15,8 +16,10 @@
 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.FinishedPartitionMappingCallback;
 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;
@@ -28,10 +31,12 @@
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-public abstract class PartitionMappingSupplierBase<T extends PartitionMappingSupplierBase<T>> {
+public abstract class PartitionMappingSupplierBase<T extends PartitionMappingSupplierBase<T>>
+    implements Finishable {
 
   private final RegisterMappingPartitionCallback registerCallback;
   private final PrepareMappingPartitionsCallback prepareCallback;
+  private final FinishedPartitionMappingCallback finishedCallback;
   private final boolean allowExperimental;
   private final byte[] metadata;
   private final MapVersion fallbackMapVersion;
@@ -45,17 +50,19 @@
   protected PartitionMappingSupplierBase(
       RegisterMappingPartitionCallback registerCallback,
       PrepareMappingPartitionsCallback prepareCallback,
+      FinishedPartitionMappingCallback finishedCallback,
       boolean allowExperimental,
       byte[] metadata,
       MapVersion fallbackMapVersion) {
     this.registerCallback = registerCallback;
     this.prepareCallback = prepareCallback;
+    this.finishedCallback = finishedCallback;
     this.allowExperimental = allowExperimental;
     this.metadata = metadata;
     this.fallbackMapVersion = fallbackMapVersion;
   }
 
-  protected MappingPartitionMetadataInternal getMetadata(DiagnosticsHandler diagnosticsHandler) {
+  public MappingPartitionMetadataInternal getMetadata(DiagnosticsHandler diagnosticsHandler) {
     if (mappingPartitionMetadataCache != null) {
       return mappingPartitionMetadataCache;
     }
@@ -96,6 +103,10 @@
         getMetadata(diagnosticsHandler).getMapVersion().toMapVersionMappingInformation());
   }
 
+  public PartitionMappingSupplier getPartitionMappingSupplier() {
+    return null;
+  }
+
   protected RetracerImpl createRetracerFromPartitionSupplier(
       DiagnosticsHandler diagnosticsHandler, MappingPartitionFromKeySupplier partitionSupplier) {
     if (!pendingKeys.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 9491483..03104f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -317,6 +317,11 @@
       keepClassInfo.forEach(
           (type, info) -> {
             DexType newType = lens.lookupType(type);
+            if (newType == options.dexItemFactory().intType) {
+              // If the enum has been unboxed, then the keep info is no longer valid. This
+              // typically happens for conditional keep rules such as -keepclassmembers.
+              return;
+            }
             assert newType == type
                 || !info.isPinned(options)
                 || info.isMinificationAllowed(options)
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 cd3c1c5..615c439 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -153,7 +153,7 @@
     }
   }
 
-  public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V20;
+  public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V21;
 
   public static final int SUPPORTED_DEX_VERSION =
       AndroidApiLevel.LATEST.getDexVersion().getIntValue();
@@ -2176,7 +2176,7 @@
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs =
         System.getProperty("com.android.tools.r8.enableEnumUnboxingDebugLogs") != null;
-    public boolean enableEnumWithSubtypesUnboxing = false;
+    public boolean enableEnumWithSubtypesUnboxing = true;
     public boolean forceRedundantConstNumberRemoval = false;
     public boolean enableExperimentalDesugaredLibraryKeepRuleGenerator = false;
     public boolean invertConditionals = false;
diff --git a/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
index 5f8d295..b04dd82 100644
--- a/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
@@ -5,15 +5,9 @@
 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 {
@@ -48,43 +42,4 @@
     }
     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/PartitionMapZipContainer.java b/src/main/java/com/android/tools/r8/utils/PartitionMapZipContainer.java
new file mode 100644
index 0000000..242fbcc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/PartitionMapZipContainer.java
@@ -0,0 +1,99 @@
+// 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.retrace.MappingPartition;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.RetracePartitionException;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class PartitionMapZipContainer {
+
+  private static final byte[] EMPTY_RESULT = new byte[0];
+  private static final String METADATA_NAME = "METADATA";
+
+  public static PartitionMappingSupplier createPartitionMapZipContainerSupplier(Path path)
+      throws Exception {
+    ZipFile zipFile = new ZipFile(path.toFile());
+    byte[] metadata =
+        ByteStreams.toByteArray(zipFile.getInputStream(zipFile.getEntry(METADATA_NAME)));
+    return PartitionMappingSupplier.builder()
+        .setMetadata(metadata)
+        .setMappingPartitionFromKeySupplier(
+            key -> {
+              try {
+                // TODO(b/274735214): The key should exist.
+                ZipEntry entry = zipFile.getEntry(key);
+                return entry == null
+                    ? EMPTY_RESULT
+                    : ByteStreams.toByteArray(zipFile.getInputStream(entry));
+              } catch (IOException e) {
+                throw new RetracePartitionException(e);
+              }
+            })
+        .setFinishedPartitionMappingCallback(
+            diagnosticsHandler -> {
+              try {
+                zipFile.close();
+              } catch (IOException e) {
+                throw new RetracePartitionException(e);
+              }
+            })
+        .build();
+  }
+
+  public static PartitionMapConsumer createPartitionMapZipContainerConsumer(Path path) {
+    return new Consumer(path);
+  }
+
+  public static class Consumer implements PartitionMapConsumer {
+
+    private final Box<ZipBuilder> zipBuilderBox = new Box<>();
+    private final Path path;
+
+    private Consumer(Path path) {
+      this.path = path;
+    }
+
+    @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_NAME, 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/test/examples/hello/keep-rules.txt b/src/test/examples/hello/keep-rules.txt
deleted file mode 100644
index 5a49e41..0000000
--- a/src/test/examples/hello/keep-rules.txt
+++ /dev/null
@@ -1 +0,0 @@
--keep class hello.Hello { public static void main(...);}
diff --git a/src/test/examples/jumbostring/JumboString.java b/src/test/examples/jumbostring/JumboString.java
deleted file mode 100644
index 9bfd9ca..0000000
--- a/src/test/examples/jumbostring/JumboString.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2016, 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 jumbostring;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-
-class JumboString {
-  public static void main(String[] args) {
-    // Make sure this string sorts after the field names and string values in the StringPoolX.java
-    // files to ensure this is a jumbo string.
-    System.out.println("zzzz - jumbo string");
-  }
-
-  // Code for generating the StringPoolX.java files.
-  //
-  // We only need to generate two files to get jumbo strings. Each file has 16k static final fields
-  // with values, and both the field name and the value will be in the string pool.
-  public static void generate() throws IOException {
-    int stringsPerFile = (1 << 14);
-    for (int fileNumber = 0; fileNumber < 2; fileNumber++) {
-      Path path = FileSystems.getDefault().getPath("StringPool" + fileNumber + ".java");
-      PrintStream out = new PrintStream(
-          Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND));
-
-      out.println(
-          "// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file");
-      out.println(
-          "// for details. All rights reserved. Use of this source code is governed by a");
-      out.println("// BSD-style license that can be found in the LICENSE file.");
-      out.println("package jumbostring;");
-      out.println();
-      out.println("class StringPool" + fileNumber + " {");
-
-      int offset = fileNumber * stringsPerFile;
-      for (int i = offset; i < offset + stringsPerFile; i++) {
-        out.println("  public static final String s" + i + " = \"" + i + "\";");
-      }
-      out.println("}");
-      out.close();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 2a5bb6c..6e40eef 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -25,50 +25,40 @@
   @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
   public static Collection<String[]> data() {
     String[] tests = {
-        "arithmetic.Arithmetic",
-        "hello.Hello",
-        "ifstatements.IfStatements",
-        "inlining.Inlining",
-        "instancevariable.InstanceVariable",
-        "instanceofstring.InstanceofString",
-        "invoke.Invoke",
-        "invokeempty.InvokeEmpty",
-        "jumbostring.JumboString",
-        "loadconst.LoadConst",
-        "loop.UdpServer",
-        "nestedtrycatches.NestedTryCatches",
-        "regalloc.RegAlloc",
-        "returns.Returns",
-        "staticfield.StaticField",
-        "stringbuilding.StringBuilding",
-        "switches.Switches",
-        "sync.Sync",
-        "throwing.Throwing",
-        "trivial.Trivial",
-        "trycatch.TryCatch",
-        "trycatchmany.TryCatchMany",
-        "regress.Regress",
-        "regress2.Regress2",
-        "regress_37726195.Regress",
-        "regress_37658666.Regress",
-        "regress_37875803.Regress",
-        "regress_37955340.Regress",
-        "regress_62300145.Regress",
-        "regress_64881691.Regress",
-        "regress_65104300.Regress",
-        "regress_70703087.Test",
-        "regress_70736958.Test",
-        "regress_70737019.Test",
-        "regress_72361252.Test",
-        "regress_110373181.Regress",
-        "memberrebinding2.Memberrebinding",
-        "memberrebinding3.Memberrebinding",
-        "minification.Minification",
-        "enclosingmethod.Main",
-        "enclosingmethod_proguarded.Main",
-        "interfaceinlining.Main",
-        "switchmaps.Switches",
-        "uninitializedfinal.UninitializedFinalFieldLeak",
+      "arithmetic.Arithmetic",
+      "inlining.Inlining",
+      "nestedtrycatches.NestedTryCatches",
+      "regalloc.RegAlloc",
+      "returns.Returns",
+      "staticfield.StaticField",
+      "stringbuilding.StringBuilding",
+      "switches.Switches",
+      "sync.Sync",
+      "throwing.Throwing",
+      "trivial.Trivial",
+      "trycatch.TryCatch",
+      "trycatchmany.TryCatchMany",
+      "regress.Regress",
+      "regress2.Regress2",
+      "regress_37726195.Regress",
+      "regress_37658666.Regress",
+      "regress_37875803.Regress",
+      "regress_37955340.Regress",
+      "regress_62300145.Regress",
+      "regress_64881691.Regress",
+      "regress_65104300.Regress",
+      "regress_70703087.Test",
+      "regress_70736958.Test",
+      "regress_70737019.Test",
+      "regress_72361252.Test",
+      "regress_110373181.Regress",
+      "memberrebinding2.Memberrebinding",
+      "memberrebinding3.Memberrebinding",
+      "minification.Minification",
+      "enclosingmethod.Main",
+      "enclosingmethod_proguarded.Main",
+      "switchmaps.Switches",
+      "uninitializedfinal.UninitializedFinalFieldLeak",
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
@@ -169,8 +159,6 @@
         // Dalvik does not correctly report the enclosing classes.
         .put(
             "enclosingmethod.Main", TestCondition.match(TestCondition.runtimesUpTo(Version.V4_4_4)))
-        // Test uses runtime methods which are not available on older Art versions.
-        .put("loop.UdpServer", TestCondition.match(TestCondition.runtimesUpTo(Version.V4_0_4)))
         .build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 8479714..7ac3869 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -138,8 +138,8 @@
 
   public static final String R8_TEST_BUCKET = "r8-test-results";
 
-  public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.4.jar";
-  public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.4.jar";
+  public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.5.jar";
+  public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.5.jar";
 
   public static final Path API_SAMPLE_JAR = Paths.get("tests", "r8_api_usage_sample.jar");
 
diff --git a/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java b/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java
index a281690..90fd31b 100644
--- a/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java
+++ b/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java
@@ -16,9 +16,11 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import java.nio.file.Path;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -70,14 +72,21 @@
   }
 
   private void checkZeroLineIsPresent(CodeInspector inspector) throws Exception {
-    // ASM skips zero lines so use javap to check the presence of a zero line.
+    // Until version 9.5, ASM skips zero lines so use javap to check the presence of a zero line.
     ClassSubject itf = inspector.clazz(I.class);
     assertThat(itf.javap(true), containsString("line 0: 0"));
+    // Having updated to ASM 9.5 we can also check using code inspector.
+    Assert.assertTrue(itf.uniqueMethod().getLineNumberTable().getLines().contains(0));
   }
 
   private void checkZeroLineNotPresent(CodeInspector inspector) throws Exception {
+    // Until version 9.5, ASM skips zero lines so use javap to check the absence of a zero line.
     ClassSubject companion = inspector.companionClassFor(I.class);
     assertThat(companion.javap(true), not(containsString("line 0: 0")));
+    // Having updated to ASM 9.5 we can also check using code inspector.
+    MethodSubject method = companion.uniqueMethod();
+    Assert.assertTrue(method.hasLineNumberTable());
+    Assert.assertFalse(method.getLineNumberTable().getLines().contains(0));
   }
 
   private byte[] getTransformedI(boolean includeZero) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/cf/LeadingZeroPositionTest.java b/src/test/java/com/android/tools/r8/cf/LeadingZeroPositionTest.java
new file mode 100644
index 0000000..28dc97e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/LeadingZeroPositionTest.java
@@ -0,0 +1,81 @@
+// 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.cf;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LeadingZeroPositionTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public LeadingZeroPositionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testZeroInInput() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    testForJvm(parameters)
+        .addProgramClassFileData(getTransformedClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkZeroLineIsPresent);
+  }
+
+  @Test
+  public void testZeroAfterD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::checkZeroLineIsPresent);
+  }
+
+  private void checkZeroLineIsPresent(CodeInspector inspector) throws Exception {
+    MethodSubject method = inspector.clazz(TestClass.class).mainMethod();
+    assertTrue(method.getLineNumberTable().getLines().contains(0));
+  }
+
+  private byte[] getTransformedClass() throws Exception {
+    return transformer(TestClass.class)
+        .setPredictiveLineNumbering(MethodPredicate.onName("main"), 0)
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.nanoTime(); // line 0 - input line, so we expect to preserve it to the output.
+      System.nanoTime(); // line 100
+      System.nanoTime(); // line 200
+      System.nanoTime(); // line 300
+      System.out.println("Hello, world"); // line 400
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MultipleZeroPositionsTest.java b/src/test/java/com/android/tools/r8/cf/MultipleZeroPositionsTest.java
new file mode 100644
index 0000000..d32a594
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/MultipleZeroPositionsTest.java
@@ -0,0 +1,122 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.LineTranslation;
+import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MultipleZeroPositionsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultCfRuntime()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public MultipleZeroPositionsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testZeroInInput() throws Exception {
+    testForJvm(parameters)
+        .addProgramClassFileData(getTransformedClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkZeroLineCount);
+  }
+
+  @Test
+  public void testZeroAfterD8() throws Exception {
+    Path out =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(getTransformedClass())
+            .setMinApi(parameters)
+            .compile()
+            .writeToZip();
+    testForJvm(parameters)
+        .addProgramFiles(out)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkZeroLineCount);
+  }
+
+  private void checkZeroLineCount(CodeInspector inspector) throws Exception {
+    int expected = 3;
+    // Until version 9.5, ASM skips zero lines so use javap to check the presence of a zero line.
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+    String javap = clazz.javap(true);
+    int actual = countOccurrences(javap, "line 0: ");
+    assertEquals(
+        "Expected " + expected + " 'line 0' entries, got " + actual + "\n" + javap,
+        expected,
+        actual);
+  }
+
+  private int countOccurrences(String string, String substring) {
+    int i = 0;
+    int found = 0;
+    while (true) {
+      i = string.indexOf(substring, i);
+      if (i == -1) {
+        return found;
+      }
+      ++found;
+      ++i;
+    }
+  }
+
+  private byte[] getTransformedClass() throws Exception {
+    return transformer(TestClass.class)
+        .setPredictiveLineNumbering(
+            new LineTranslation() {
+              Int2IntMap map = new Int2IntOpenHashMap();
+
+              @Override
+              public int translate(MethodContext context, int inputLine) {
+                if (context.getReference().getMethodName().equals("main")) {
+                  return map.computeIfAbsent(
+                      inputLine, ignore -> map.size() % 2 == 0 ? 1 + map.size() : 0);
+                }
+                return inputLine;
+              }
+            })
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.nanoTime(); // line
+      System.nanoTime(); // 0
+      System.nanoTime(); // line
+      System.nanoTime(); // 0
+      System.out.println("Hello, world");
+      ; // line
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index fe5441b..9227df7 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.bootstrap;
 
 import static com.android.tools.r8.graph.GenericSignatureIdentityTest.testParseSignaturesInJar;
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.examples.hello.HelloTestRunner;
 import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.ProguardMappingSupplier;
 import com.android.tools.r8.retrace.Retrace;
@@ -51,14 +51,14 @@
 
   private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
 
-  private static final String HELLO_NAME = "hello.Hello";
+  private static final String HELLO_NAME = HelloTestRunner.getHelloClass().getTypeName();
   private static final String[] KEEP_HELLO = {
     "-keep class " + HELLO_NAME + " {",
     "  public static void main(...);",
     "}",
     "-allowaccessmodification"
   };
-  private static String HELLO_EXPECTED = StringUtils.lines("Hello, world");
+  private static String HELLO_EXPECTED = HelloTestRunner.getExpectedOutput();
 
   private static Pair<Path, Path> r8R8Debug;
   private static Pair<Path, Path> r8R8Release;
@@ -199,7 +199,7 @@
 
   @Test
   public void test() throws Exception {
-    Path program = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
+    Path program = HelloTestRunner.writeHelloProgramJar(temp);
     testForJvm(parameters)
         .addProgramFiles(program)
         .run(parameters.getRuntime(), HELLO_NAME)
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
index 0210341..87bbfaf 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.examples.hello.HelloTestRunner;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Charsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -30,7 +30,8 @@
   private static final Path R8_STABLE_JAR =
       Paths.get("third_party", "r8-releases", "3.2.54", "r8.jar");
 
-  private static final String HELLO_EXPECTED = StringUtils.lines("Hello World!");
+  private static final Class<?> HELLO_CLASS = HelloTestRunner.getHelloClass();
+  private static final String HELLO_EXPECTED = HelloTestRunner.getExpectedOutput();
 
   private static class R8Result {
 
@@ -59,18 +60,18 @@
   }
 
   private Path getHelloInputs() {
-    return ToolHelper.getClassFileForTestClass(Hello.class);
+    return ToolHelper.getClassFileForTestClass(HELLO_CLASS);
   }
 
   private String getHelloKeepRules() {
-    return TestBase.keepMainProguardConfiguration(Hello.class);
+    return TestBase.keepMainProguardConfiguration(HELLO_CLASS);
   }
 
   @Test
   public void reference() throws Exception {
     testForJvm(parameters)
         .addProgramFiles(getHelloInputs())
-        .run(parameters.getRuntime(), Hello.class)
+        .run(parameters.getRuntime(), HELLO_CLASS)
         .assertSuccessWithOutput(HELLO_EXPECTED);
   }
 
@@ -90,7 +91,7 @@
         runExternalR8(R8_STABLE_JAR, getHelloInputs(), getHelloKeepRules(), mode);
     testForJvm(parameters)
         .addProgramFiles(helloCompiledWithR8.outputJar)
-        .run(parameters.getRuntime(), Hello.class)
+        .run(parameters.getRuntime(), HELLO_CLASS)
         .assertSuccessWithOutput(HELLO_EXPECTED);
 
     compareR8(helloCompiledWithR8, mode);
@@ -151,11 +152,4 @@
     String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
     return new R8Result(processResult, outputJar, pgMap);
   }
-
-  public static class Hello {
-
-    public static void main(String[] args) {
-      System.out.println("Hello World!");
-    }
-  }
 }
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 7696491..88c7358 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -60,47 +60,6 @@
   }
 
   @Test
-  public void testHello() throws Exception {
-    testDebugging("hello", "Hello");
-  }
-
-  @Test
-  public void testIfStatements() throws Exception {
-    testDebugging("ifstatements", "IfStatements");
-  }
-
-  @Test
-  public void testInstanceVariable() throws Exception {
-    testDebugging("instancevariable", "InstanceVariable");
-  }
-
-  @Test
-  public void testInstanceofString() throws Exception {
-    testDebugging("instanceofstring", "InstanceofString");
-  }
-
-  @Test
-  public void testInvoke() throws Exception {
-    testDebugging("invoke", "Invoke");
-  }
-
-  @Test
-  public void testJumboString() throws Exception {
-    testDebugging("jumbostring", "JumboString");
-  }
-
-  @Test
-  public void testLoadConst() throws Exception {
-    testDebugging("loadconst", "LoadConst");
-  }
-
-  @Test
-  public void testUdpServer() throws Exception {
-    // TODO(b/79671093): We don't match JVM's behavior on this example.
-    testDebuggingJvmOutputOnly("loop", "UdpServer");
-  }
-
-  @Test
   public void testRegAlloc() throws Exception {
     testDebugging("regalloc", "RegAlloc");
   }
@@ -159,11 +118,6 @@
   }
 
   @Test
-  public void testInvokeEmpty() throws Exception {
-    testDebugging("invokeempty", "InvokeEmpty");
-  }
-
-  @Test
   public void testRegress() throws Exception {
     testDebugging("regress", "Regress");
   }
@@ -257,11 +211,6 @@
   }
 
   @Test
-  public void testInterfaceinlining() throws Exception {
-    testDebugging("interfaceinlining", "Main");
-  }
-
-  @Test
   public void testSwitchmaps() throws Exception {
     // TODO(b/79671093): D8 has different line number info during stepping.
     testDebuggingJvmOnly("switchmaps", "Switches");
diff --git a/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
index acbe0e5..0436b0d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
@@ -62,8 +62,8 @@
                 getClassWithZeroLineEntry(), Reference.classFromClass(TestClass.class)))
         .run(parameters.getRuntime(), TestClass.class)
         .assertFailureWithErrorThatThrows(RuntimeException.class)
-        // If this becomes zero ASM has been updated to not assign line zero a special meaning.
-        .inspectOriginalStackTrace(st -> checkLineNumber(st, 1));
+        // From version 9.5 ASM was updated to not assign line zero a special meaning.
+        .inspectOriginalStackTrace(st -> checkLineNumber(st, 0));
   }
 
   private void checkLineNumber(StackTrace st, int lineNumber) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
index 3831e44..1e47370 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
 import java.util.Collections;
@@ -48,7 +49,7 @@
   private byte[] getTestClassTransformed() throws IOException {
     return transformer(TestClass.class)
         .setSourceFile(INPUT_SOURCE_FILE)
-        .setPredictiveLineNumbering()
+        .setPredictiveLineNumbering(MethodPredicate.all(), -1 /* start with no line */)
         .transform();
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/Base64Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/Base64Test.java
new file mode 100644
index 0000000..22eb7b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/Base64Test.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Base64;
+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 Base64Test extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("YWJj", "abc");
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationSpecification compilationSpecification;
+
+  @Parameters(name = "{0}, spec: {1}, {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        ImmutableList.of(JDK11_PATH),
+        SPECIFICATIONS_WITH_CF2CF);
+  }
+
+  public Base64Test(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationSpecification = compilationSpecification;
+  }
+
+  @Test
+  public void testBase64() throws Throwable {
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      String encodedText = Base64.getEncoder().encodeToString("abc".getBytes(UTF_8));
+      System.out.println(encodedText);
+      String decodedText = new String(Base64.getDecoder().decode(encodedText));
+      System.out.println(decodedText);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodsListTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodsListTest.java
new file mode 100644
index 0000000..451d62c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodsListTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_LEGACY;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_MINIMAL;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.DesugaredMethodsList;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+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 DesugaredMethodsListTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+  private List<String> lintContents;
+
+  @Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().build(),
+        ImmutableList.of(JDK8, JDK11_MINIMAL, JDK11, JDK11_PATH, JDK11_LEGACY));
+  }
+
+  public DesugaredMethodsListTest(
+      TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
+    this.parameters = parameters;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+  }
+
+  private boolean supportsAllMethodsOf(String type) {
+    return lintContents.contains(type);
+  }
+
+  private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
+    // Just do some light probing in the generated lint files.
+    lintContents = FileUtils.readAllLines(lintFile);
+
+    // All methods supported on BiFunction with maintain prefix.
+    assertEquals(
+        minApiLevel.isLessThan(AndroidApiLevel.N),
+        supportsAllMethodsOf("java/util/function/BiFunction"));
+
+    assertEquals(
+        minApiLevel.isLessThan(AndroidApiLevel.O)
+            && libraryDesugaringSpecification != JDK11_MINIMAL,
+        supportsAllMethodsOf("java/time/MonthDay"));
+
+    // File should be sorted.
+    List<String> sorted = new ArrayList<>(lintContents);
+    sorted.sort(Comparator.naturalOrder());
+    assertEquals(lintContents, sorted);
+  }
+
+  @Test
+  public void testLint() throws Exception {
+    Path output = temp.newFile("lint.txt").toPath();
+    Path jdkLibJar =
+        libraryDesugaringSpecification == JDK8
+            ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
+            : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
+
+    AndroidApiLevel minApi = parameters.getRuntime().asDex().getMinApiLevel();
+    DesugaredMethodsList.main(
+        new String[] {
+          String.valueOf(minApi.getLevel()),
+          libraryDesugaringSpecification.getSpecification().toString(),
+          jdkLibJar.toString(),
+          output.toString(),
+          ToolHelper.getAndroidJar(AndroidApiLevel.U).toString()
+        });
+    checkFileContent(minApi, output);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index 0ad53ed..2ccc573 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -25,7 +25,7 @@
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateLintFiles;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateDesugaredLibraryLintFiles;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.WrapperDescriptor;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -227,10 +227,10 @@
 
   @Test
   public void test() throws Exception {
-    CodeInspector desugaredApiJar = getDesugaredApiJar();
     Set<ClassReference> preDesugarTypes = getPreDesugarTypes();
 
-    DexItemFactory factory = new DexItemFactory();
+    CodeInspector nonDesugaredJar = new CodeInspector(ToolHelper.getAndroidJar(targetApi));
+    DexItemFactory factory = nonDesugaredJar.getFactory();
     DesugaredLibrarySpecification spec =
         DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
             StringResource.fromFile(libraryDesugaringSpecification.getSpecification()),
@@ -270,11 +270,10 @@
               genericConversionsInSpec.put(method.toString(), indexes);
             });
 
-    CodeInspector nonDesugaredJar = new CodeInspector(ToolHelper.getAndroidJar(targetApi));
     Set<DexEncodedMethod> genericDependencies = new HashSet<>();
     Map<ClassReference, Set<MethodReference>> directWrappers =
         getDirectlyReferencedWrapperTypes(
-            desugaredApiJar,
+            specification,
             preDesugarTypes,
             nonDesugaredJar,
             customConversionsOnly,
@@ -365,7 +364,7 @@
   }
 
   private Map<ClassReference, Set<MethodReference>> getDirectlyReferencedWrapperTypes(
-      CodeInspector desugaredApiJar,
+      MachineDesugaredLibrarySpecification specification,
       Set<ClassReference> preDesugarTypes,
       CodeInspector nonDesugaredJar,
       Set<String> customConversions,
@@ -374,37 +373,29 @@
       Set<DexEncodedMethod> genericDependencies) {
     Map<ClassReference, Set<MethodReference>> directWrappers = new HashMap<>();
     nonDesugaredJar.forAllClasses(
-        clazz -> {
-          clazz.forAllMethods(
-              method -> {
-                if (!method.isPublic() && !method.isProtected()) {
-                  return;
-                }
-                // We check the holder type to avoid dealing with methods on desugared types which
-                // are present in Android.jar and not in the desugared library, specifically on
-                // JDK 8 desugared library.
-                if (desugaredApiJar
-                    .clazz(method.getMethod().getHolderType().asClassReference())
-                    .isPresent()) {
-                  return;
-                }
-                Consumer<ClassReference> adder =
-                    t -> {
-                      if (t.toString().contains("HexFormat")
-                          || t.toString().contains("IsoCountryCode")) {
-                        System.out.println("x");
-                      }
-                      directWrappers
-                          .computeIfAbsent(t, k -> new HashSet<>())
-                          .add(method.asMethodReference());
-                    };
-                forEachType(
-                    method,
-                    t -> addType(adder, t, preDesugarTypes, customConversions, maintainType),
-                    genericConversionsInSpec,
-                    genericDependencies);
-              });
-        });
+        clazz ->
+            clazz.forAllMethods(
+                method -> {
+                  if (!method.isPublic() && !method.isProtected()) {
+                    return;
+                  }
+                  // We check the holder type to avoid dealing with methods on desugared types which
+                  // are present in Android.jar and not in the desugared library, specifically on
+                  // JDK 8 desugared library.
+                  if (specification.isSupported(method.getMethod().getReference())) {
+                    return;
+                  }
+                  Consumer<ClassReference> adder =
+                      t ->
+                          directWrappers
+                              .computeIfAbsent(t, k -> new HashSet<>())
+                              .add(method.asMethodReference());
+                  forEachType(
+                      method,
+                      t -> addType(adder, t, preDesugarTypes, customConversions, maintainType),
+                      genericConversionsInSpec,
+                      genericDependencies);
+                }));
     return directWrappers;
   }
 
@@ -512,8 +503,8 @@
 
   private CodeInspector getDesugaredApiJar() throws Exception {
     Path out = temp.newFolder().toPath();
-    GenerateLintFiles desugaredApi =
-        GenerateLintFiles.createForTesting(
+    GenerateDesugaredLibraryLintFiles desugaredApi =
+        GenerateDesugaredLibraryLintFiles.createForTesting(
             libraryDesugaringSpecification.getSpecification(),
             libraryDesugaringSpecification.getDesugarJdkLibs(),
             out,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index aeedca9..202d752 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -20,8 +20,8 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateDesugaredLibraryLintFiles;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateHtmlDoc;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateLintFiles;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -166,7 +166,7 @@
         libraryDesugaringSpecification == JDK8
             ? ToolHelper.DESUGARED_JDK_8_LIB_JAR
             : LibraryDesugaringSpecification.getTempLibraryJDK11Undesugar();
-    GenerateLintFiles.main(
+    GenerateDesugaredLibraryLintFiles.main(
         new String[] {
           libraryDesugaringSpecification.getSpecification().toString(),
           jdkLibJar.toString(),
@@ -192,12 +192,10 @@
           "desugared_apis_" + requiredCompilationApiLevel.getLevel() + "_" + minApiLevel.getLevel();
       if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) {
         assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-        assertTrue(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
         checkFileContent(
             minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"));
       } else {
         assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")));
-        assertFalse(Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar")));
       }
     }
   }
@@ -247,7 +245,7 @@
           .run(spec + ".html");
       Path lint = top.resolve("lint_" + spec);
       Files.createDirectories(lint);
-      new GenerateLintFiles(
+      new GenerateDesugaredLibraryLintFiles(
               spec.getSpecification().toString(),
               jdkLibJar.toString(),
               lint.toString(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index b7a1a0b..3a166dd 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
 
@@ -20,8 +19,8 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestCompileResult;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.examples.hello.HelloTestRunner;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -63,13 +62,13 @@
     return Paths.get(string).toAbsolutePath().toString();
   }
 
-  private static final String HELLO_KEEP =
-      commandLinePathFor("src/test/examples/hello/keep-rules.txt");
-  private static final String HELLO_PATH =
-      commandLinePathFor(ToolHelper.EXAMPLES_BUILD_DIR + "hello" + JAR_EXTENSION);
+  private static final String HELLO_NAME = typeName(HelloTestRunner.getHelloClass());
 
   @Test
   public void testHelloCompiledWithR8Dex() throws Exception {
+    Path keepRules =
+        writeTextToTempFile(keepMainProguardConfiguration(HELLO_NAME)).toAbsolutePath();
+    Path helloInput = HelloTestRunner.writeHelloProgramJar(temp);
     Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
     compileR8ToDexWithD8()
         .run(
@@ -81,14 +80,15 @@
             "--lib",
             commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
             "--pg-conf",
-            HELLO_KEEP,
-            HELLO_PATH)
+            keepRules.toString(),
+            helloInput.toString())
         .assertSuccess();
     verifyResult(helloOutput);
   }
 
   @Test
   public void testHelloCompiledWithD8Dex() throws Exception {
+    Path helloInput = HelloTestRunner.writeHelloProgramJar(temp).toAbsolutePath();
     Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
     compileR8ToDexWithD8()
         .run(
@@ -99,14 +99,14 @@
             helloOutput.toString(),
             "--lib",
             commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
-            HELLO_PATH)
+            helloInput.toString())
         .assertSuccess();
     verifyResult(helloOutput);
   }
 
   private void verifyResult(Path helloOutput) throws IOException {
-    ProcessResult processResult = ToolHelper.runArtRaw(helloOutput.toString(), "hello.Hello");
-    assertEquals(StringUtils.lines("Hello, world"), processResult.stdout);
+    ProcessResult processResult = ToolHelper.runArtRaw(helloOutput.toString(), HELLO_NAME);
+    assertEquals(HelloTestRunner.getExpectedOutput(), processResult.stdout);
   }
 
   private DesugaredLibraryTestCompileResult<?> compileR8ToDexWithD8() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
index 2ed69e1..8d2a21e 100644
--- a/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/r8bootstrap/Java17R8BootstrapTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.r8bootstrap;
 
 import static com.android.tools.r8.desugar.r8bootstrap.JavaBootstrapUtils.MAIN_KEEP;
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static junit.framework.TestCase.assertEquals;
 
 import com.android.tools.r8.Jdk11TestUtils;
@@ -15,9 +14,9 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.examples.hello.HelloTestRunner;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -35,8 +34,9 @@
 @RunWith(Parameterized.class)
 public class Java17R8BootstrapTest extends TestBase {
 
+  private static final Class<?> HELLO_CLASS = HelloTestRunner.getHelloClass();
   private static final String[] HELLO_KEEP = {
-    "-keep class hello.Hello {  public static void main(...);}"
+    "-keep class " + HELLO_CLASS.getTypeName() + " {  public static void main(...);}"
   };
 
   private static Path r8Lib17NoDesugar;
@@ -85,17 +85,20 @@
     Assume.assumeTrue(supportsSealedClassesWhenGeneratingCf());
     Path prevGeneratedJar = null;
     String prevRunResult = null;
+    Path helloJar = HelloTestRunner.writeHelloProgramJar(temp);
     for (Path jar : jarsToCompare()) {
       Path generatedJar =
           testForExternalR8(Backend.CF, parameters.getRuntime())
               .useProvidedR8(jar)
-              .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION))
+              .addProgramFiles(helloJar)
               .addKeepRules(HELLO_KEEP)
               .compile()
               .outputJar();
       String runResult =
           ToolHelper.runJava(
-                  parameters.getRuntime().asCf(), ImmutableList.of(generatedJar), "hello.Hello")
+                  parameters.getRuntime().asCf(),
+                  ImmutableList.of(generatedJar),
+                  HELLO_CLASS.getTypeName())
               .toString();
       if (prevRunResult != null) {
         assertEquals(prevRunResult, runResult);
@@ -117,11 +120,12 @@
     Assume.assumeTrue(JavaBootstrapUtils.exists(ToolHelper.R8_WITH_RELOCATED_DEPS_17_JAR));
     Assume.assumeTrue(supportsSealedClassesWhenGeneratingCf());
     Path prevGeneratedJar = null;
+    Path helloJar = HelloTestRunner.writeHelloProgramJar(temp);
     for (Path jar : jarsToCompare()) {
       Path generatedJar =
           testForExternalR8(Backend.CF, parameters.getRuntime())
               .useProvidedR8(jar)
-              .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION))
+              .addProgramFiles(helloJar)
               .addKeepRuleFiles(MAIN_KEEP)
               .compile()
               .outputJar();
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/CallToOtherEnumCompareToMethodNegativeUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/CallToOtherEnumCompareToMethodNegativeUnboxingTest.java
index f190a34..f589118 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/CallToOtherEnumCompareToMethodNegativeUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/CallToOtherEnumCompareToMethodNegativeUnboxingTest.java
@@ -56,6 +56,7 @@
 
   static class Main {
 
+    @SuppressWarnings("unchecked")
     public static void main(String[] args) {
       Enum<?> fooEnum = Foo.A;
       Enum<Bar> fooEnumInDisguise = (Enum<Bar>) fooEnum;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
index 82ffe50..99c92ec 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringOverrideEnumUnboxingTest.java
@@ -41,7 +41,7 @@
         .addKeepRules(enumKeepRules.getKeepRules())
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .addEnumUnboxingInspector(
-            inspector -> inspector.assertNotUnboxed(EnumNameToString.MyEnum.class))
+            inspector -> inspector.assertUnboxed(EnumNameToString.MyEnum.class))
         .setMinApi(parameters)
         .compile()
         .run(parameters.getRuntime(), success)
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
index 9335996..be3e6b3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractEnumMergingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/AbstractEnumMergingTest.java
@@ -37,7 +37,6 @@
         .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))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/BasicEnumMergingKeepSubtypeTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/BasicEnumMergingKeepSubtypeTest.java
new file mode 100644
index 0000000..4f1d44e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/BasicEnumMergingKeepSubtypeTest.java
@@ -0,0 +1,80 @@
+// 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 static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 BasicEnumMergingKeepSubtypeTest 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();
+  }
+
+  private static final String SUBTYPE_NAME = EnumWithVirtualOverride.class.getTypeName() + "$1";
+
+  public BasicEnumMergingKeepSubtypeTest(
+      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("-keep class " + SUBTYPE_NAME + " { public void method(); }")
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
+        .addEnumUnboxingInspector(
+            inspector -> inspector.assertNotUnboxed(EnumWithVirtualOverride.class))
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::methodKept)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "B");
+  }
+
+  private void methodKept(CodeInspector inspector) {
+    assertTrue(inspector.clazz(SUBTYPE_NAME).uniqueMethodWithFinalName("method").isPresent());
+  }
+
+  enum EnumWithVirtualOverride {
+    A {
+      public void method() {
+        System.out.println("a");
+      }
+    },
+    B;
+
+    public void method() {
+      System.out.println(name());
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      EnumWithVirtualOverride.A.method();
+      EnumWithVirtualOverride.B.method();
+    }
+  }
+}
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 0eb13fb..cd6339b 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
@@ -37,7 +37,6 @@
         .addInnerClasses(BasicEnumMergingTest.class)
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
         .addEnumUnboxingInspector(
             inspector ->
                 inspector
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
index 00bb1cf..0eca471 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/DeterministicEnumMergingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/DeterministicEnumMergingTest.java
@@ -39,7 +39,6 @@
             .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))
@@ -50,7 +49,6 @@
             .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))
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/EnumMergingInstantiatingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/EnumMergingInstantiatingTest.java
index 074e0e9..07c72a2 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/EnumMergingInstantiatingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/EnumMergingInstantiatingTest.java
@@ -55,7 +55,6 @@
         .addProgramClassFileData(PROGRAM_CLASSES_DATA)
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(InstantiatingEnum.class))
         .enableInliningAnnotations()
         .enableAlwaysInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/StaticEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/StaticEnumMergingTest.java
index 1c1b779..4c5e8bc 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/StaticEnumMergingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/StaticEnumMergingTest.java
@@ -47,7 +47,6 @@
         .addProgramFiles(JDK17_JAR)
         .addKeepMainRule(MAIN)
         .addKeepRules(enumKeepRules.getKeepRules())
-        .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .addOptionsModification(opt -> opt.testing.enableEnumUnboxingDebugLogs = true)
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperEnumMergingTest.java
index 7a2b509..1bb41ca 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperEnumMergingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperEnumMergingTest.java
@@ -43,7 +43,6 @@
         .addInnerClasses(SuperEnumMergingTest.class)
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumWithSuper.class))
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperToStringEnumMergingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperToStringEnumMergingTest.java
index a88db9a..7d7fbfe 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperToStringEnumMergingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/enummerging/SuperToStringEnumMergingTest.java
@@ -41,7 +41,6 @@
         .addInnerClasses(SuperToStringEnumMergingTest.class)
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        .addOptionsModification(opt -> opt.testing.enableEnumWithSubtypesUnboxing = true)
         .addEnumUnboxingInspector(
             inspector -> inspector.assertUnboxed(EnumToString.class, EnumToStringOverride.class))
         .enableInliningAnnotations()
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 11576d6..47c84e8 100644
--- a/src/test/java/com/android/tools/r8/examples/ExamplesTestBase.java
+++ b/src/test/java/com/android/tools/r8/examples/ExamplesTestBase.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.examples;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
@@ -13,7 +12,6 @@
 import com.android.tools.r8.debug.DebugStreamComparator;
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestConfig;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
@@ -32,7 +30,7 @@
 
   public abstract Class<?> getMainClass();
 
-  public List<Class<?>> getTestClasses() {
+  public List<Class<?>> getTestClasses() throws Exception {
     return Collections.singletonList(getMainClass());
   }
 
@@ -83,7 +81,7 @@
     comparator.compare();
   }
 
-  private CfDebugTestConfig getJvmConfig() throws IOException {
+  private CfDebugTestConfig getJvmConfig() throws Exception {
     // We can't use `testForJvm` as we want to build the reference even for non-representative API.
     CfRuntime cfRuntime =
         parameters.isCfRuntime() ? parameters.asCfRuntime() : CfRuntime.getDefaultCfRuntime();
@@ -92,7 +90,7 @@
     return new CfDebugTestConfig(cfRuntime, Collections.singletonList(jar));
   }
 
-  private DebugTestConfig getD8Config() throws CompilationFailedException {
+  private DebugTestConfig getD8Config() throws Exception {
     return testForD8(parameters.getBackend())
         .addProgramClasses(getTestClasses())
         .setMinApi(parameters)
@@ -100,7 +98,7 @@
         .debugConfig(parameters.getRuntime());
   }
 
-  private DebugTestConfig getR8Config() throws CompilationFailedException {
+  private DebugTestConfig getR8Config() throws Exception {
     return testForR8(parameters.getBackend())
         .setMinApi(parameters)
         .addProgramClasses(getTestClasses())
diff --git a/src/test/examples/hello/Hello.java b/src/test/java/com/android/tools/r8/examples/hello/Hello.java
similarity index 90%
rename from src/test/examples/hello/Hello.java
rename to src/test/java/com/android/tools/r8/examples/hello/Hello.java
index c58f329..0be083c 100644
--- a/src/test/examples/hello/Hello.java
+++ b/src/test/java/com/android/tools/r8/examples/hello/Hello.java
@@ -5,7 +5,7 @@
 // This code is not run directly. It needs to be compiled to dex code.
 // 'hello.dex' is what is run.
 
-package hello;
+package com.android.tools.r8.examples.hello;
 
 class Hello {
   public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/examples/hello/HelloTestRunner.java b/src/test/java/com/android/tools/r8/examples/hello/HelloTestRunner.java
new file mode 100644
index 0000000..cb305d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/hello/HelloTestRunner.java
@@ -0,0 +1,69 @@
+// 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.hello;
+
+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 java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class HelloTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  // The "hello" program is reused in various tests via these static methods.
+
+  public static Class<?> getHelloClass() {
+    return Hello.class;
+  }
+
+  public static String getExpectedOutput() {
+    return StringUtils.lines("Hello, world");
+  }
+
+  public static Path writeHelloProgramJar(TemporaryFolder temp) throws IOException {
+    Path jar = temp.newFolder().toPath().resolve("hello.jar");
+    writeClassesToJar(jar, Hello.class);
+    return jar;
+  }
+
+  public HelloTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return getHelloClass();
+  }
+
+  @Override
+  public String getExpected() {
+    return getExpectedOutput();
+  }
+
+  @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/ifstatements/IfStatements.java b/src/test/java/com/android/tools/r8/examples/ifstatements/IfStatements.java
similarity index 96%
rename from src/test/examples/ifstatements/IfStatements.java
rename to src/test/java/com/android/tools/r8/examples/ifstatements/IfStatements.java
index 5d340ab..41183af 100644
--- a/src/test/examples/ifstatements/IfStatements.java
+++ b/src/test/java/com/android/tools/r8/examples/ifstatements/IfStatements.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2016, 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 ifstatements;
+package com.android.tools.r8.examples.ifstatements;
 
 class IfStatements {
 
diff --git a/src/test/java/com/android/tools/r8/examples/ifstatements/IfStatementsTestRunner.java b/src/test/java/com/android/tools/r8/examples/ifstatements/IfStatementsTestRunner.java
new file mode 100644
index 0000000..9106b03
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/ifstatements/IfStatementsTestRunner.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.examples.ifstatements;
+
+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 IfStatementsTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public IfStatementsTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return IfStatements.class;
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines(
+        "sisnotnull",
+        "sisnull",
+        "ifCond x != 0",
+        "ifCond x < 0",
+        "ifCond x <= 0",
+        "ifCond x == 0",
+        "ifCond x >= 0",
+        "ifCond x <= 0",
+        "ifCond x != 0",
+        "ifCond x >= 0",
+        "ifCond x > 0",
+        "ifIcmp x != y",
+        "ifIcmp x < y",
+        "ifIcmp x <= y",
+        "ifIcmp x == y",
+        "ifIcmp x >= y",
+        "ifIcmp x <= y",
+        "ifIcmp x != y",
+        "ifIcmp x >= y",
+        "ifIcmp x > y",
+        "ifAcmp a == b",
+        "ifAcmp 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/instanceofstring/InstanceOfStringTestRunner.java b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceOfStringTestRunner.java
new file mode 100644
index 0000000..14b2620
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceOfStringTestRunner.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.instanceofstring;
+
+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 InstanceOfStringTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InstanceOfStringTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return InstanceofString.class;
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("is-string:true");
+  }
+
+  @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/instanceofstring/InstanceofString.java b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceofString.java
similarity index 86%
rename from src/test/examples/instanceofstring/InstanceofString.java
rename to src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceofString.java
index c7ff011..8073d0a 100644
--- a/src/test/examples/instanceofstring/InstanceofString.java
+++ b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceofString.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2016, 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 instanceofstring;
+package com.android.tools.r8.examples.instanceofstring;
 
 class InstanceofString {
   public static void main(String[] args) {
diff --git a/src/test/examples/instancevariable/InstanceVariable.java b/src/test/java/com/android/tools/r8/examples/instancevariable/InstanceVariable.java
similarity index 93%
rename from src/test/examples/instancevariable/InstanceVariable.java
rename to src/test/java/com/android/tools/r8/examples/instancevariable/InstanceVariable.java
index 88768f1..fdad644 100644
--- a/src/test/examples/instancevariable/InstanceVariable.java
+++ b/src/test/java/com/android/tools/r8/examples/instancevariable/InstanceVariable.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2016, 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 instancevariable;
+package com.android.tools.r8.examples.instancevariable;
 
 class InstanceVariable {
   public int foo = 42;
diff --git a/src/test/java/com/android/tools/r8/examples/instancevariable/InstanceVariableTestRunner.java b/src/test/java/com/android/tools/r8/examples/instancevariable/InstanceVariableTestRunner.java
new file mode 100644
index 0000000..95b20d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/instancevariable/InstanceVariableTestRunner.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.instancevariable;
+
+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 InstanceVariableTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InstanceVariableTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return InstanceVariable.class;
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("144=144");
+  }
+
+  @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/interfaceinlining/InterfaceInliningTestRunner.java b/src/test/java/com/android/tools/r8/examples/interfaceinlining/InterfaceInliningTestRunner.java
new file mode 100644
index 0000000..05138f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/interfaceinlining/InterfaceInliningTestRunner.java
@@ -0,0 +1,56 @@
+// 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.interfaceinlining;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+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 InterfaceInliningTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InterfaceInliningTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return Main.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() {
+    return ImmutableList.of(Main.class, Main.Data.class, Main.DataI.class);
+  }
+
+  @Override
+  public String getExpected() {
+    return "true";
+  }
+
+  @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/interfaceinlining/Main.java b/src/test/java/com/android/tools/r8/examples/interfaceinlining/Main.java
similarity index 93%
rename from src/test/examples/interfaceinlining/Main.java
rename to src/test/java/com/android/tools/r8/examples/interfaceinlining/Main.java
index 865fa14..2c23c4d 100644
--- a/src/test/examples/interfaceinlining/Main.java
+++ b/src/test/java/com/android/tools/r8/examples/interfaceinlining/Main.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package interfaceinlining;
+package com.android.tools.r8.examples.interfaceinlining;
 
 // This test ensures a check cast instruction is inserted IFF
 // the expression "((DataI) other).field()" is inlined.
diff --git a/src/test/examples/invoke/Invoke.java b/src/test/java/com/android/tools/r8/examples/invoke/Invoke.java
similarity index 99%
rename from src/test/examples/invoke/Invoke.java
rename to src/test/java/com/android/tools/r8/examples/invoke/Invoke.java
index d936140..6625471 100644
--- a/src/test/examples/invoke/Invoke.java
+++ b/src/test/java/com/android/tools/r8/examples/invoke/Invoke.java
@@ -5,7 +5,7 @@
 // This code is not run directly. It needs to be compiled to dex code.
 // 'invoke.dex' is what is run.
 
-package invoke;
+package com.android.tools.r8.examples.invoke;
 
 public class Invoke extends SuperClass implements InvokeInterface {
 
diff --git a/src/test/examples/invoke/InvokeInterface.java b/src/test/java/com/android/tools/r8/examples/invoke/InvokeInterface.java
similarity index 91%
rename from src/test/examples/invoke/InvokeInterface.java
rename to src/test/java/com/android/tools/r8/examples/invoke/InvokeInterface.java
index 7f5a08e..bba0c12 100644
--- a/src/test/examples/invoke/InvokeInterface.java
+++ b/src/test/java/com/android/tools/r8/examples/invoke/InvokeInterface.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package invoke;
+package com.android.tools.r8.examples.invoke;
 
 public interface InvokeInterface {
   public void interface0();
diff --git a/src/test/java/com/android/tools/r8/examples/invoke/InvokeTestRunner.java b/src/test/java/com/android/tools/r8/examples/invoke/InvokeTestRunner.java
new file mode 100644
index 0000000..5e22ac6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/invoke/InvokeTestRunner.java
@@ -0,0 +1,117 @@
+// 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.invoke;
+
+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 InvokeTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InvokeTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return Invoke.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() {
+    return ImmutableList.of(Invoke.class, InvokeInterface.class, SuperClass.class);
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines(
+        "static0",
+        "static1 1",
+        "static2 1 2",
+        "static3 1 2 3",
+        "static4 1 2 3 4",
+        "static5 1 2 3 4 5",
+        "staticRange 1 2 3 4 5 6",
+        "staticDouble0 0.1",
+        "staticDouble2 0.1 0.2",
+        "staticDoubleRange 0.1 0.2 0.3",
+        "direct0",
+        "direct1 1",
+        "direct2 1 2",
+        "direct3 1 2 3",
+        "direct4 1 2 3 4",
+        "directRange 1 2 3 4 5 6",
+        "interface0",
+        "interface1 1",
+        "interface2 1 2",
+        "interface3 1 2 3",
+        "interface4 1 2 3 4",
+        "interfaceRange 1 2 3 4 5 6",
+        "virtual0",
+        "virtual1 1",
+        "virtual2 1 2",
+        "virtual3 1 2 3",
+        "virtual4 1 2 3 4",
+        "virtualRange 1 2 3 4 5 6",
+        "super0",
+        "super1 1",
+        "super2 1 2",
+        "super3 1 2 3",
+        "super4 1 2 3 4",
+        "superRange 1 2 3 4 5 6",
+        "rangeInvoke0 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke0 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke1 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke1 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke2 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke2 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "oneArgumentMethod 0",
+        "oneArgumentMethod 3",
+        "oneArgumentMethod 16",
+        "twoArgumentMethod 16 9",
+        "twoArgumentMethod 16 10",
+        "twoArgumentMethod 16 11",
+        "oneDoubleArgumentMethod 1.1",
+        "oneDoubleArgumentMethod 4.4",
+        "oneDoubleArgumentMethod 16.6",
+        "twoDoubleArgumentMethod 16.6 9.9",
+        "twoDoubleArgumentMethod 16.6 10.1",
+        "twoDoubleArgumentMethod 16.6 11.2",
+        "rangeInvokesRepeatedArgument0 0 1 0 1 0 1 0 1",
+        "rangeInvokesRepeatedArgument0 0 1 1 1 1 1 1 1",
+        "int: 42",
+        "double: 42.42",
+        "int: 32",
+        "double: 32.32",
+        "519",
+        "15");
+  }
+
+  @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/invoke/SuperClass.java b/src/test/java/com/android/tools/r8/examples/invoke/SuperClass.java
similarity index 95%
rename from src/test/examples/invoke/SuperClass.java
rename to src/test/java/com/android/tools/r8/examples/invoke/SuperClass.java
index c4f7ae5..9b1deb2 100644
--- a/src/test/examples/invoke/SuperClass.java
+++ b/src/test/java/com/android/tools/r8/examples/invoke/SuperClass.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package invoke;
+package com.android.tools.r8.examples.invoke;
 
 public class SuperClass {
   public void super0() {
diff --git a/src/test/examples/invokeempty/ClassA.java b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassA.java
similarity index 86%
rename from src/test/examples/invokeempty/ClassA.java
rename to src/test/java/com/android/tools/r8/examples/invokeempty/ClassA.java
index 50ac63c..248643f 100644
--- a/src/test/examples/invokeempty/ClassA.java
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassA.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package invokeempty;
+package com.android.tools.r8.examples.invokeempty;
 
 public class ClassA {
 
diff --git a/src/test/examples/invokeempty/ClassB.java b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassB.java
similarity index 86%
rename from src/test/examples/invokeempty/ClassB.java
rename to src/test/java/com/android/tools/r8/examples/invokeempty/ClassB.java
index 305baa6..dd96e04 100644
--- a/src/test/examples/invokeempty/ClassB.java
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassB.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package invokeempty;
+package com.android.tools.r8.examples.invokeempty;
 
 public class ClassB {
 
diff --git a/src/test/examples/invokeempty/InvokeEmpty.java b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmpty.java
similarity index 88%
rename from src/test/examples/invokeempty/InvokeEmpty.java
rename to src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmpty.java
index 6160eb2..e1969bf 100644
--- a/src/test/examples/invokeempty/InvokeEmpty.java
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmpty.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package invokeempty;
+package com.android.tools.r8.examples.invokeempty;
 
 public class InvokeEmpty {
 
diff --git a/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmptyTestRunner.java b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmptyTestRunner.java
new file mode 100644
index 0000000..967fba6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmptyTestRunner.java
@@ -0,0 +1,56 @@
+// 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.invokeempty;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+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 InvokeEmptyTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InvokeEmptyTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return InvokeEmpty.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() {
+    return ImmutableList.of(getMainClass(), ClassA.class, ClassB.class);
+  }
+
+  @Override
+  public String getExpected() {
+    return "AB";
+  }
+
+  @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/jumbostring/JumboString.java b/src/test/java/com/android/tools/r8/examples/jumbostring/JumboString.java
new file mode 100644
index 0000000..024040a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/jumbostring/JumboString.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2016, 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.jumbostring;
+
+class JumboString {
+  public static void main(String[] args) {
+    // Make sure this string sorts after the field names and string values in the StringPoolX.java
+    // files to ensure this is a jumbo string.
+    System.out.println("zzzz - jumbo string");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/examples/jumbostring/JumboStringTestRunner.java b/src/test/java/com/android/tools/r8/examples/jumbostring/JumboStringTestRunner.java
new file mode 100644
index 0000000..dd474a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/jumbostring/JumboStringTestRunner.java
@@ -0,0 +1,95 @@
+// 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.jumbostring;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class JumboStringTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public JumboStringTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return JumboString.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() {
+    return ImmutableList.of(getMainClass(), StringPool0.class, StringPool1.class);
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("zzzz - jumbo string");
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+
+  // Code for generating the StringPoolX.java files.
+  //
+  // We only need to generate two files to get jumbo strings. Each file has 16k static final fields
+  // with values, and both the field name and the value will be in the string pool.
+  public static void generate() throws IOException {
+    Path jumboExampleDir =
+        ToolHelper.getSourceFileForTestClass(JumboStringTestRunner.class).getParent();
+    int stringsPerFile = (1 << 14);
+    for (int fileNumber = 0; fileNumber < 2; fileNumber++) {
+      Path path = jumboExampleDir.resolve("StringPool" + fileNumber + ".java");
+      PrintStream out =
+          new PrintStream(
+              Files.newOutputStream(
+                  path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+
+      out.println("// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file");
+      out.println("// for details. All rights reserved. Use of this source code is governed by a");
+      out.println("// BSD-style license that can be found in the LICENSE file.");
+      out.println("package " + JumboStringTestRunner.class.getPackage().getName() + ";");
+      out.println();
+      out.println("// GENERATED FILE - DO NOT EDIT (See JumboStringTestRunner.generate)");
+      out.println("class StringPool" + fileNumber + " {");
+
+      int offset = fileNumber * stringsPerFile;
+      for (int i = offset; i < offset + stringsPerFile; i++) {
+        out.println("  public static final String s" + i + " = \"" + i + "\";");
+      }
+      out.println("}");
+      out.close();
+    }
+  }
+}
diff --git a/src/test/examples/jumbostring/StringPool0.java b/src/test/java/com/android/tools/r8/examples/jumbostring/StringPool0.java
similarity index 99%
rename from src/test/examples/jumbostring/StringPool0.java
rename to src/test/java/com/android/tools/r8/examples/jumbostring/StringPool0.java
index e6e3b4b..bf42c57 100644
--- a/src/test/examples/jumbostring/StringPool0.java
+++ b/src/test/java/com/android/tools/r8/examples/jumbostring/StringPool0.java
@@ -1,8 +1,9 @@
 // Copyright (c) 2016, 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 jumbostring;
+package com.android.tools.r8.examples.jumbostring;
 
+// GENERATED FILE - DO NOT EDIT (See JumboStringTestRunner.generate)
 class StringPool0 {
   public static final String s0 = "0";
   public static final String s1 = "1";
diff --git a/src/test/examples/jumbostring/StringPool1.java b/src/test/java/com/android/tools/r8/examples/jumbostring/StringPool1.java
similarity index 99%
rename from src/test/examples/jumbostring/StringPool1.java
rename to src/test/java/com/android/tools/r8/examples/jumbostring/StringPool1.java
index 8539fb2..77fa3bb 100644
--- a/src/test/examples/jumbostring/StringPool1.java
+++ b/src/test/java/com/android/tools/r8/examples/jumbostring/StringPool1.java
@@ -1,8 +1,9 @@
 // Copyright (c) 2016, 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 jumbostring;
+package com.android.tools.r8.examples.jumbostring;
 
+// GENERATED FILE - DO NOT EDIT (See JumboStringTestRunner.generate)
 class StringPool1 {
   public static final String s16384 = "16384";
   public static final String s16385 = "16385";
diff --git a/src/test/examples/loadconst/LoadConst.java b/src/test/java/com/android/tools/r8/examples/loadconst/LoadConst.java
similarity index 90%
rename from src/test/examples/loadconst/LoadConst.java
rename to src/test/java/com/android/tools/r8/examples/loadconst/LoadConst.java
index 7ba7f30..37ca26a 100644
--- a/src/test/examples/loadconst/LoadConst.java
+++ b/src/test/java/com/android/tools/r8/examples/loadconst/LoadConst.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2016, 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 loadconst;
+package com.android.tools.r8.examples.loadconst;
 
 public class LoadConst {
 
diff --git a/src/test/java/com/android/tools/r8/examples/loadconst/LoadConstTestRunner.java b/src/test/java/com/android/tools/r8/examples/loadconst/LoadConstTestRunner.java
new file mode 100644
index 0000000..c0e18a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/loadconst/LoadConstTestRunner.java
@@ -0,0 +1,51 @@
+// 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.loadconst;
+
+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 LoadConstTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public LoadConstTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return LoadConst.class;
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines(
+        "21474836471234.5foobar", "class com.android.tools.r8.examples.loadconst.LoadConst");
+  }
+
+  @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/loadconst/UdpServerTestRunner.java b/src/test/java/com/android/tools/r8/examples/loadconst/UdpServerTestRunner.java
new file mode 100644
index 0000000..c925874
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/loadconst/UdpServerTestRunner.java
@@ -0,0 +1,67 @@
+// 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.loadconst;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.android.tools.r8.examples.loop.UdpServer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UdpServerTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        // Test uses Executors.newWorkStealingPool introduced at API 24.
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .withAllRuntimes()
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public UdpServerTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return UdpServer.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() throws Exception {
+    return ImmutableList.of(getMainClass(), Class.forName(getMainClass().getTypeName() + "$1"));
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("java.util.concurrent.TimeoutException");
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  // TODO(b/79671093): We don't match JVM's behavior on this example (line differences).
+  @Ignore("b/79671093")
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+}
diff --git a/src/test/examples/loop/UdpServer.java b/src/test/java/com/android/tools/r8/examples/loop/UdpServer.java
similarity index 96%
rename from src/test/examples/loop/UdpServer.java
rename to src/test/java/com/android/tools/r8/examples/loop/UdpServer.java
index 463bfde..c76a3ba 100644
--- a/src/test/examples/loop/UdpServer.java
+++ b/src/test/java/com/android/tools/r8/examples/loop/UdpServer.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package loop;
+package com.android.tools.r8.examples.loop;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 36cfc16..7d981a5 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -9,8 +9,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.Version;
@@ -20,6 +22,7 @@
 import com.android.tools.r8.retrace.stacktraces.PGStackTrace;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
 import java.io.ByteArrayOutputStream;
@@ -44,9 +47,9 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class RetraceCommandLineTests {
+public class RetraceCommandLineTests extends TestBase {
 
-  private static String SMILEY_EMOJI = "\uD83D\uDE00";
+  private static final String SMILEY_EMOJI = "\uD83D\uDE00";
 
   private static final String WAITING_MESSAGE =
       "Waiting for stack-trace input..." + StringUtils.LINE_SEPARATOR;
@@ -54,14 +57,16 @@
   @Rule public TemporaryFolder folder = new TemporaryFolder();
 
   private final boolean testExternal;
+  private final boolean testPartition;
 
-  @Parameters(name = "external: {0}")
-  public static Boolean[] data() {
-    return BooleanUtils.values();
+  @Parameters(name = "external: {0}, partition: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(BooleanUtils.values(), BooleanUtils.values());
   }
 
-  public RetraceCommandLineTests(boolean testExternal) {
+  public RetraceCommandLineTests(boolean testExternal, boolean testPartition) {
     this.testExternal = testExternal;
+    this.testPartition = testPartition;
   }
 
   @Test
@@ -207,6 +212,7 @@
 
   @Test
   public void testHelpMessageOnStdIn() throws IOException {
+    assumeFalse(testPartition);
     ProcessResult processResult = runRetrace("", "", true);
     assertTrue(processResult.stdout.startsWith(WAITING_MESSAGE));
   }
@@ -290,10 +296,16 @@
       throws IOException {
     Path mappingFile = folder.newFile("mapping.txt").toPath();
     Files.write(mappingFile, mapping.getBytes());
+    if (testPartition) {
+      mappingFile = runPartitionCommandLine(mappingFile);
+    }
     File stackTraceFile = folder.newFile("stacktrace.txt");
     Files.write(stackTraceFile.toPath(), stackTrace.getBytes(StandardCharsets.UTF_8));
 
     Collection<String> args = new ArrayList<>();
+    if (testPartition) {
+      args.add("--partition-map");
+    }
     args.add(mappingFile.toString());
     if (!stacktraceStdIn) {
       args.add(stackTraceFile.toPath().toString());
@@ -302,8 +314,29 @@
     return runRetraceCommandLine(stacktraceStdIn ? stackTraceFile : null, args);
   }
 
+  private Path runPartitionCommandLine(Path mappingFile) throws IOException {
+    Path partitionOutput = folder.newFile("partition.txt").toPath();
+    ProcessResult processResult =
+        runCommandLine(
+            null,
+            "com.android.tools.r8.retrace.Partition",
+            Partition::run,
+            ImmutableList.of("--output", partitionOutput.toString(), mappingFile.toString()));
+    assertEquals(0, processResult.exitCode);
+    return partitionOutput;
+  }
+
   private ProcessResult runRetraceCommandLine(File stdInput, Collection<String> args)
       throws IOException {
+    return runCommandLine(stdInput, "com.android.tools.r8.retrace.Retrace", Retrace::run, args);
+  }
+
+  private <E extends Exception> ProcessResult runCommandLine(
+      File stdInput,
+      String mainEntryPointExternal,
+      ThrowingConsumer<String[], E> mainEntryPointInternal,
+      Collection<String> args)
+      throws IOException {
     if (testExternal) {
       // The external dependency is built on top of R8Lib. If test.py is run with
       // no r8lib, do not try and run the external R8 Retrace since it has not been built.
@@ -314,7 +347,7 @@
       command.add("-ea");
       command.add("-cp");
       command.add(ToolHelper.R8_RETRACE_JAR.toString());
-      command.add("com.android.tools.r8.retrace.Retrace");
+      command.add(mainEntryPointExternal);
       command.addAll(args);
       ProcessBuilder builder = new ProcessBuilder(command);
       if (stdInput != null) {
@@ -336,7 +369,7 @@
       try {
         String[] strArgs = new String[0];
         strArgs = args.toArray(strArgs);
-        Retrace.run(strArgs);
+        mainEntryPointInternal.accept(strArgs);
       } catch (Throwable t) {
         exitCode = 1;
       }
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
index 3836c08..f4a9316 100644
--- a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
 import com.android.tools.r8.retrace.PartitionedToProguardMappingConverter;
 import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy;
@@ -61,10 +62,13 @@
 
     StringBuilder builder = new StringBuilder();
     PartitionedToProguardMappingConverter.builder()
-        .setMetadata(metadataData.getBytes())
         .setDiagnosticsHandler(diagnosticsHandler)
+        .setPartitionMappingSupplier(
+            PartitionMappingSupplier.builder()
+                .setMetadata(metadataData.getBytes())
+                .setMappingPartitionFromKeySupplier(partitions::get)
+                .build())
         .setConsumer((string, handler) -> builder.append(string))
-        .setPartitionSupplier(partitions::get)
         .build()
         .run();
     List<String> joinedMapLines = StringUtils.splitLines(builder.toString());
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 63573d2..e010aa2 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1282,7 +1282,7 @@
   }
 
   public ClassFileTransformer setPredictiveLineNumbering(MethodPredicate predicate) {
-    return setPredictiveLineNumbering(predicate, 0);
+    return setPredictiveLineNumbering(predicate, 100);
   }
 
   public interface LineTranslation {
@@ -1306,7 +1306,8 @@
         int nextLine = lines.getOrDefault(method, startingLineNumber);
         // Increment the actual line content by 100 so that each one is clearly distinct
         // from a PC value for any of the methods.
-        lines.put(method, nextLine + 100);
+        int nextNextLine = nextLine == -1 ? 100 : nextLine + 100;
+        lines.put(method, nextNextLine);
         return nextLine;
       }
       return line;
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
index f548507..6e6661d 100644
--- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@
-99369b53116d6ab88384f57930b203cd7de55c0a
\ No newline at end of file
+e1e8d0122222037f2db92e639a636ec51b24b911
\ No newline at end of file
diff --git a/tools/asmifier.py b/tools/asmifier.py
index 82b90af..504f2bf 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -10,7 +10,7 @@
 import sys
 import utils
 
-ASM_VERSION = '9.4'
+ASM_VERSION = '9.5'
 ASM_JAR = 'asm-' + ASM_VERSION + '.jar'
 ASM_UTIL_JAR = 'asm-util-' + ASM_VERSION + '.jar'
 
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 7d73a93..ee68e1e 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -381,7 +381,7 @@
         jdk.GetJavaExecutable(),
         '-cp',
         utils.R8_JAR,
-        'com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateLintFiles',
+        'com.android.tools.r8.ir.desugar.desugaredlibrary.lint.GenerateDesugaredLibraryLintFiles',
         configuration,
         implementation,
         lint_dir]