Merge commit 'f2a9fcf875f650017c6a5593096baafc6b05ca51' into dev-release

Change-Id: I266691d5986b918d75dc65af6a1177fdf46adfcb
diff --git a/.gitignore b/.gitignore
index ba2303e..bb6648e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
 !third_party/chrome/*.sha1
-!third_party/gmail/*.sha1
 !third_party/gmscore/*.sha1
 !third_party/internal/*.sha1
 !third_party/nest/*.sha1
@@ -48,8 +47,8 @@
 third_party/android_jar/lib
 third_party/android_jar/lib-v[0-9][0-9]
 third_party/android_jar/lib-v[0-9][0-9].tar.gz
-third_party/android_jar/lib-master
-third_party/android_jar/lib-master.tar.gz
+third_party/android_jar/lib-main
+third_party/android_jar/lib-main.tar.gz
 third_party/android_jar/lib.tar.gz
 third_party/android_jar/libcore_latest
 third_party/android_jar/libcore_latest.tar.gz
@@ -111,7 +110,6 @@
 third_party/examplesAndroidPGenerated.tar.gz
 third_party/framework
 third_party/framework.tar.gz
-third_party/gmail/*
 third_party/gmscore/*
 third_party/google/google-java-format/1.14.0
 third_party/google/google-java-format/1.14.0.tar.gz
diff --git a/compatibility-faq.md b/compatibility-faq.md
index cb33077..37109c4 100644
--- a/compatibility-faq.md
+++ b/compatibility-faq.md
@@ -29,6 +29,10 @@
 `-keep[classmembers],allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification class-specification`
 Additionally, for attributes describing a relationship such as `InnerClass` and
 `EnclosingMethod`, non-compat mode requires both endpoints being kept.
+- When optimizing or minifying the `SourceFile` attribute will always be
+rewritten to `SourceFile` unless `-renamesourcefileattribute` is used in which
+case the provided value is used. The original source file name is in the mapping
+file and when optimizing or minifying a mapping file is always produced.
 
 ## Stack traces and retracing
 When compiling with R8, a mapping file can be produced to support mapping stack
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index b1ca8e7..121a668 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -644,7 +644,7 @@
 fun getThirdPartyAndroidJars() : List<ThirdPartyDependency> {
   return listOf(
     "libcore_latest",
-    "lib-master",
+    "lib-main",
     "lib-v14",
     "lib-v15",
     "lib-v19",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 0020bab..f2dfda6 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -63,14 +63,6 @@
       "rewrite_prefix": {
         "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
       },
-      "emulate_interface": {
-        "java.util.Collection": {
-          "rewrittenType": "j$.util.Collection",
-          "emulatedMethods": [
-            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
-          ]
-        }
-      },
       "retarget_method": {
         "java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
         "java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -99,6 +91,14 @@
       "rewrite_prefix": {
         "java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
       },
+      "emulate_interface": {
+        "java.util.Collection": {
+          "rewrittenType": "j$.util.Collection",
+          "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+          ]
+        }
+      },
       "retarget_method": {
         "java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
         "java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -252,6 +252,7 @@
         "java.util.Collection": {
           "rewrittenType": "j$.util.Collection",
           "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
             "java.util.stream.Stream java.util.Collection#stream()",
             "java.util.stream.Stream java.util.Collection#parallelStream()",
             "java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index aa8b1c0..1aff889 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -77,14 +77,6 @@
         "java.io.DesugarInputStream": "j$.io.DesugarInputStream",
         "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
       },
-      "emulate_interface": {
-        "java.util.Collection": {
-          "rewrittenType": "j$.util.Collection",
-          "emulatedMethods": [
-            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
-          ]
-        }
-      },
       "retarget_method": {
         "java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
         "java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -119,6 +111,14 @@
       "rewrite_prefix": {
         "java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
       },
+      "emulate_interface": {
+        "java.util.Collection": {
+          "rewrittenType": "j$.util.Collection",
+          "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+          ]
+        }
+      },
       "retarget_method": {
         "java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
         "java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -402,6 +402,7 @@
         "java.util.Collection": {
           "rewrittenType": "j$.util.Collection",
           "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
             "java.util.stream.Stream java.util.Collection#stream()",
             "java.util.stream.Stream java.util.Collection#parallelStream()",
             "java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 46f1ce4..85d86a6 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -771,7 +771,9 @@
     internal.configureDesugaredLibrary(desugaredLibrarySpecification, synthesizedClassPrefix);
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
-    if (!enableMissingLibraryApiModeling) {
+    if (internal.isGeneratingClassFiles()
+        || (System.getProperty("com.android.tools.r8.enableApiOutliningAndStubbing") == null
+            && !enableMissingLibraryApiModeling)) {
       internal.apiModelingOptions().disableApiCallerIdentification();
       internal.apiModelingOptions().disableOutliningAndStubbing();
     }
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
index 88e9f55..28125f6 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
@@ -31,7 +31,7 @@
    * <p>The context class is the class for which the global synthetic data is needed. If compiling
    * in DexIndexed mode, the context class will be null.
    *
-   * <p>The accept method will be called at most once for a given context class (any only once at
+   * <p>The accept method will be called at most once for a given context class (and only once at
    * all for a DexIndexed mode compilation). The global data for that class may be the same as for
    * other context classes, but it will be provided for each context.
    *
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index c9590ea..ccfe752 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -34,11 +34,12 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ThrowExceptionCode;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
+import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.naming.RecordRewritingNamingLens;
 import com.android.tools.r8.naming.VarHandleDesugaringRewritingNamingLens;
@@ -72,15 +73,14 @@
 @KeepForApi
 public class GlobalSyntheticsGenerator {
 
-  @SuppressWarnings("ReferenceEquality")
   private static boolean ensureAllGlobalSyntheticsModeled(SyntheticNaming naming) {
     for (SyntheticKind kind : naming.kinds()) {
       assert !kind.isGlobal()
           || !kind.isMayOverridesNonProgramType()
-          || kind == naming.RECORD_TAG
-          || kind == naming.API_MODEL_STUB
-          || kind == naming.METHOD_HANDLES_LOOKUP
-          || kind == naming.VAR_HANDLE;
+          || kind.equals(naming.RECORD_TAG)
+          || kind.equals(naming.API_MODEL_STUB)
+          || kind.equals(naming.METHOD_HANDLES_LOOKUP)
+          || kind.equals(naming.VAR_HANDLE);
     }
     return true;
   }
@@ -129,8 +129,13 @@
               timing.end();
 
               assert GlobalSyntheticsGeneratorVerifier.verifyExpectedClassesArePresent(appView);
-
-              ApplicationWriter.create(appView, options.getMarker()).write(executorService, app);
+              if (options.isGeneratingDex()) {
+                ApplicationWriter.create(appView, options.getMarker()).write(executorService, app);
+              } else {
+                assert options.isGeneratingClassFiles();
+                new CfApplicationWriter(appView, options.getMarker())
+                    .write(options.getClassFileConsumer(), app);
+              }
             } catch (ExecutionException e) {
               throw unwrapExecutionException(e);
             } catch (IOException e) {
@@ -202,11 +207,18 @@
     VarHandleDesugaring.ensureMethodHandlesLookupClass(
         appView, varHandleEventConsumer, synthesizingContext);
 
-    IRConverter converter = new IRConverter(appView);
-    converter.processSimpleSynthesizeMethods(methodsToProcess, executorService);
+    // Commit all the synthetics to the program and then convert as per D8.
+    // We must run proper D8 conversion as the global synthetics may give rise to additional
+    // synthetics as part of their implementation.
+    assert appView.getSyntheticItems().hasPendingSyntheticClasses();
+    appView.setAppInfo(
+        new AppInfo(
+            appView.appInfo().getSyntheticItems().commit(appView.app()),
+            appView.appInfo().getMainDexInfo()));
+
+    new PrimaryD8L8IRConverter(appView, Timing.empty()).convert(appView, executorService);
 
     appView
-        .withoutClassHierarchy()
         .setAppInfo(
             new AppInfo(
                 appView.appInfo().getSyntheticItems().commit(appView.app()),
@@ -221,11 +233,12 @@
         VarHandleDesugaringRewritingNamingLens.createVarHandleDesugaringRewritingNamingLens(
             appView));
 
-    // Add global synthetic classes for api stubs.
-    createAllApiStubs(appView, synthesizingContext, executorService);
+    if (appView.options().isGeneratingDex()) {
+      // Add global synthetic classes for api stubs.
+      createAllApiStubs(appView, synthesizingContext, executorService);
+    }
 
     appView
-        .withoutClassHierarchy()
         .setAppInfo(
             new AppInfo(
                 appView.appInfo().getSyntheticItems().commit(appView.app()),
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
index 44f54ba..9ffc2e8 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
@@ -7,21 +7,25 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Set;
 
 /**
  * Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler.
@@ -29,9 +33,10 @@
 @KeepForApi
 public final class GlobalSyntheticsGeneratorCommand {
 
-  private final ProgramConsumer programConsumer;
+  private final GlobalSyntheticsConsumer globalsConsumer;
   private final Reporter reporter;
   private final int minApiLevel;
+  private final boolean classfileDesugaringOnly;
 
   private final boolean printHelp;
   private final boolean printVersion;
@@ -41,10 +46,15 @@
   private final DexItemFactory factory = new DexItemFactory();
 
   private GlobalSyntheticsGeneratorCommand(
-      AndroidApp inputApp, ProgramConsumer programConsumer, Reporter reporter, int minApiLevel) {
+      AndroidApp inputApp,
+      GlobalSyntheticsConsumer globalsConsumer,
+      Reporter reporter,
+      int minApiLevel,
+      boolean classfileDesugaringOnly) {
     this.inputApp = inputApp;
-    this.programConsumer = programConsumer;
+    this.globalsConsumer = globalsConsumer;
     this.minApiLevel = minApiLevel;
+    this.classfileDesugaringOnly = classfileDesugaringOnly;
     this.reporter = reporter;
     this.printHelp = false;
     this.printVersion = false;
@@ -55,8 +65,9 @@
     this.printVersion = printVersion;
 
     this.inputApp = null;
-    this.programConsumer = null;
+    this.globalsConsumer = null;
     this.minApiLevel = AndroidApiLevel.B.getLevel();
+    this.classfileDesugaringOnly = false;
 
     reporter = new Reporter();
   }
@@ -130,9 +141,15 @@
     assert !internal.debug;
     assert !internal.minimalMainDex;
     internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minApiLevel));
-    assert !internal.intermediate;
     assert internal.retainCompileTimeAnnotations;
-    internal.programConsumer = programConsumer;
+    internal.intermediate = true;
+    internal.programConsumer =
+        classfileDesugaringOnly ? new ThrowingCfConsumer() : new ThrowingDexConsumer();
+    internal.setGlobalSyntheticsConsumer(globalsConsumer);
+    if (classfileDesugaringOnly) {
+      internal.apiModelingOptions().disableApiCallerIdentification();
+      internal.apiModelingOptions().disableOutliningAndStubbing();
+    }
 
     // Assert and fixup defaults.
     assert !internal.isShrinking();
@@ -148,6 +165,33 @@
     return internal;
   }
 
+  private static class ThrowingCfConsumer implements ClassFileConsumer {
+
+    @Override
+    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+      throw new Unreachable("Unexpected attempt to write a non-global artifact");
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      // Nothing to do.
+    }
+  }
+
+  private static class ThrowingDexConsumer implements DexIndexedConsumer {
+
+    @Override
+    public void accept(
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+      throw new Unreachable("Unexpected attempt to write a non-global artifact");
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      // Nothing to do.
+    }
+  }
+
   /**
    * Builder for constructing a GlobalSyntheticsGeneratorCommand.
    *
@@ -156,9 +200,10 @@
   @KeepForApi
   public static class Builder {
 
-    private ProgramConsumer programConsumer = null;
+    private GlobalSyntheticsConsumer globalsConsumer = null;
     private final Reporter reporter;
     private int minApiLevel = AndroidApiLevel.B.getLevel();
+    private boolean classfileDesugaringOnly = false;
     private boolean printHelp = false;
     private boolean printVersion = false;
     private final AndroidApp.Builder appBuilder = AndroidApp.builder();
@@ -177,6 +222,11 @@
       return this;
     }
 
+    public Builder setClassfileDesugaringOnly(boolean value) {
+      this.classfileDesugaringOnly = value;
+      return this;
+    }
+
     /** Set the value of the print-help flag. */
     public Builder setPrintHelp(boolean printHelp) {
       this.printHelp = printHelp;
@@ -210,17 +260,32 @@
       return this;
     }
 
-    /** Set an output path to consume the resulting program. */
-    public Builder setProgramConsumerOutput(Path path) {
-      return setProgramConsumer(
-          FileUtils.isArchive(path)
-              ? new DexIndexedConsumer.ArchiveConsumer(path, false)
-              : new DexIndexedConsumer.DirectoryConsumer(path, false));
+    /** Set a destination to write the resulting global synthetics output file. */
+    public Builder setGlobalSyntheticsOutput(Path path) {
+      return setGlobalSyntheticsConsumer(
+          new GlobalSyntheticsConsumer() {
+
+            private boolean written = false;
+
+            @Override
+            public synchronized void accept(
+                ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+              if (written) {
+                throw new Unreachable("Unexpected attempt to repeatedly write global synthetics");
+              }
+              written = true;
+              try {
+                Files.write(path, data.copyByteData());
+              } catch (IOException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
     }
 
-    /** Set a consumer for obtaining the resulting program. */
-    public Builder setProgramConsumer(ProgramConsumer programConsumer) {
-      this.programConsumer = programConsumer;
+    /** Set a consumer for obtaining the resulting global synthetics output. */
+    public Builder setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalsConsumer) {
+      this.globalsConsumer = globalsConsumer;
       return this;
     }
 
@@ -230,7 +295,7 @@
         return new GlobalSyntheticsGeneratorCommand(printHelp, printVersion);
       }
       return new GlobalSyntheticsGeneratorCommand(
-          appBuilder.build(), programConsumer, reporter, minApiLevel);
+          appBuilder.build(), globalsConsumer, reporter, minApiLevel, classfileDesugaringOnly);
     }
 
     private boolean isPrintHelpOrPrintVersion() {
@@ -241,9 +306,8 @@
       if (isPrintHelpOrPrintVersion()) {
         return;
       }
-      if (!(programConsumer instanceof DexIndexedConsumer)) {
-        reporter.error(
-            "GlobalSyntheticsGenerator does not support compiling to dex per class or class files");
+      if (globalsConsumer == null) {
+        reporter.error("GlobalSyntheticsGenerator does not support compiling without output");
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
index 9022e28..de61833 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
@@ -21,6 +21,7 @@
 
   private static final String LOWER_CASE_NAME = "globalsyntheticsgenerator";
   private static final String MIN_API_FLAG = "--min-api";
+  private static final String CLASSFILE_DESUGARING_MODE = "--classfile";
 
   private static final String USAGE_MESSAGE =
       StringUtils.lines("Usage: " + LOWER_CASE_NAME + " [options] " + "where options are:");
@@ -29,7 +30,14 @@
     return ImmutableList.<ParseFlagInfo>builder()
         .add(ParseFlagInfoImpl.getMinApi())
         .add(ParseFlagInfoImpl.getLib())
-        .add(ParseFlagInfoImpl.flag1("--output", "<dex-file>", "Output result in <dex-file>."))
+        .add(
+            ParseFlagInfoImpl.flag1(
+                "--output", "<globals-file>", "Output result in <globals-file>."))
+        .add(
+            ParseFlagInfoImpl.flag0(
+                "--classfile",
+                "Generate globals for only classfile to classfile desugaring.",
+                "(By default globals for both classfile and dex desugaring are generated)."))
         .add(ParseFlagInfoImpl.getVersion(LOWER_CASE_NAME))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -98,6 +106,8 @@
         }
       } else if (arg.equals("--lib")) {
         builder.addLibraryFiles(Paths.get(nextArg));
+      } else if (arg.equals(CLASSFILE_DESUGARING_MODE)) {
+        builder.setClassfileDesugaringOnly(true);
       } else if (arg.startsWith("--")) {
         builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
       }
@@ -105,6 +115,6 @@
     if (outputPath == null) {
       outputPath = Paths.get(".");
     }
-    return builder.setProgramConsumerOutput(outputPath);
+    return builder.setGlobalSyntheticsOutput(outputPath);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7597634..819b349 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1264,6 +1264,9 @@
       horizontalClassMergerOptions.disable();
       // R8 CF output does not support desugaring so disable it.
       internal.desugarState = DesugarState.OFF;
+      // TODO(b/333477035): Since D8 dexing now supports outline/stubbing API calls R8/CF should
+      //  likely disable API caller identification too so as not to prevent inlining.
+      internal.apiModelingOptions().disableOutliningAndStubbing();
     }
 
     // EXPERIMENTAL flags.
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
index 3821b7f..aaf01ff 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelCompute.java
@@ -21,14 +21,14 @@
   public AndroidApiLevelCompute() {
     knownApiLevelCache = new KnownApiLevel[AndroidApiLevel.API_DATABASE_LEVEL.getLevel() + 1];
     for (AndroidApiLevel value : AndroidApiLevel.values()) {
-      if (value != AndroidApiLevel.MASTER) {
+      if (value != AndroidApiLevel.MAIN) {
         knownApiLevelCache[value.getLevel()] = new KnownApiLevel(value);
       }
     }
   }
 
   public KnownApiLevel of(AndroidApiLevel apiLevel) {
-    if (apiLevel == AndroidApiLevel.MASTER) {
+    if (apiLevel == AndroidApiLevel.MAIN) {
       return ComputedApiLevel.master();
     }
     return knownApiLevelCache[apiLevel.getLevel()];
@@ -73,7 +73,7 @@
   }
 
   public ComputedApiLevel computeInitialMinApiLevel(InternalOptions options) {
-    if (options.getMinApiLevel() == AndroidApiLevel.MASTER) {
+    if (options.getMinApiLevel() == AndroidApiLevel.MAIN) {
       return ComputedApiLevel.master();
     }
     return new KnownApiLevel(options.getMinApiLevel());
diff --git a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
index 24b7251..5e5c280 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ComputedApiLevel.java
@@ -161,7 +161,7 @@
 
   class KnownApiLevel implements ComputedApiLevel {
 
-    private static final KnownApiLevel MASTER_INSTANCE = new KnownApiLevel(AndroidApiLevel.MASTER);
+    private static final KnownApiLevel MASTER_INSTANCE = new KnownApiLevel(AndroidApiLevel.MAIN);
 
     private final AndroidApiLevel apiLevel;
 
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 04ff802..5e31c47 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -189,13 +189,13 @@
     if (inputApp.hasMainDexList()) {
       for (StringResource resource : inputApp.getMainDexListResources()) {
         if (emitDeprecatedDiagnostics) {
-          options.reporter.warning(new UnsupportedMainDexListUsageDiagnostic(resource.getOrigin()));
+          options.reporter.error(new UnsupportedMainDexListUsageDiagnostic(resource.getOrigin()));
         }
         addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, itemFactory));
       }
       if (!inputApp.getMainDexClasses().isEmpty()) {
         if (emitDeprecatedDiagnostics) {
-          options.reporter.warning(new UnsupportedMainDexListUsageDiagnostic(Origin.unknown()));
+          options.reporter.error(new UnsupportedMainDexListUsageDiagnostic(Origin.unknown()));
         }
         addToMainDexClasses(
             app,
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 6694cb0..4fdbb35 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -89,6 +89,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -216,7 +217,12 @@
       Collection<DexProgramClass> allClasses = classes;
       classes = new ArrayList<>(allClasses.size());
       for (DexProgramClass clazz : allClasses) {
-        if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+        if (appView.getSyntheticItems().isGlobalSyntheticClassTransitive(clazz)) {
+          Consumer<DexProgramClass> globalSyntheticCreatedCallback =
+              appView.options().testing.globalSyntheticCreatedCallback;
+          if (globalSyntheticCreatedCallback != null) {
+            globalSyntheticCreatedCallback.accept(clazz);
+          }
           globalSynthetics.add(clazz);
         } else {
           classes.add(clazz);
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index e096d1e..42e9f77 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -259,15 +259,6 @@
         defaultTypeRewriter(appInfo));
   }
 
-  public static <T extends AppInfo> AppView<T> createForSimulatingD8InR8(T appInfo) {
-    return new AppView<>(
-        appInfo,
-        ArtProfileCollection.empty(),
-        StartupProfile.empty(),
-        WholeProgramOptimizations.OFF,
-        defaultTypeRewriter(appInfo));
-  }
-
   public static AppView<AppInfoWithClassHierarchy> createForSimulatingR8InD8(
       DirectMappedDexApplication application, MainDexInfo mainDexInfo) {
     ClassToFeatureSplitMap classToFeatureSplitMap =
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 6f89669..9d6d32e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -526,6 +526,15 @@
     return false;
   }
 
+  public boolean hasThrowingInstructions() {
+    for (DexInstruction instruction : instructions) {
+      if (instruction.canThrow()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   @Override
   public Code asCode() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
index 3250adb..30e2f54 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -12,14 +12,30 @@
 
 public class DexTypeUtils {
 
+  public static DexType computeApiSafeLeastUpperBound(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
+    DexType leastUpperBound = computeLeastUpperBound(appView, types);
+    return findApiSafeUpperBound(appView, leastUpperBound);
+  }
+
   public static DexType computeLeastUpperBound(
       AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
     TypeElement join =
         TypeElement.join(Iterables.transform(types, type -> type.toTypeElement(appView)), appView);
-    return findApiSafeUpperBound(appView, toDexType(appView, join));
+    return toDexType(appView, join);
   }
 
-  @SuppressWarnings("ReferenceEquality")
+  public static boolean isApiSafe(
+      AppView<? extends AppInfoWithClassHierarchy> appView, DexType type) {
+    DexType apiSafeUpperBound = findApiSafeUpperBound(appView, type);
+    return apiSafeUpperBound.isIdenticalTo(type);
+  }
+
+  public static boolean isLeastUpperBoundApiSafe(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
+    return isApiSafe(appView, computeLeastUpperBound(appView, types));
+  }
+
   public static DexType toDexType(
       AppView<? extends AppInfoWithClassHierarchy> appView, TypeElement type) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
@@ -33,7 +49,7 @@
     }
     assert type.isClassType();
     ClassTypeElement classType = type.asClassType();
-    if (classType.getClassType() != dexItemFactory.objectType) {
+    if (classType.getClassType().isNotIdenticalTo(dexItemFactory.objectType)) {
       return classType.getClassType();
     }
     if (classType.getInterfaces().hasSingleKnownInterface()) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 778132e..6c98445 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -189,7 +189,7 @@
       DexEncodedField newField;
       if (needsRelaxedType(targetField, sourceFields)) {
         DexType newFieldType =
-            DexTypeUtils.computeLeastUpperBound(
+            DexTypeUtils.computeApiSafeLeastUpperBound(
                 appView,
                 Iterables.transform(
                     Iterables.concat(IterableUtils.singleton(targetField), sourceFields),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 0db2480..3499c30 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -77,7 +77,6 @@
 
   private ClassMerger(
       AppView<?> appView,
-      IRCodeProvider codeProvider,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       HorizontalMergeGroup group,
       Collection<VirtualMethodMerger> virtualMethodMergers) {
@@ -93,8 +92,7 @@
     // Method mergers.
     this.classInitializerMerger = ClassInitializerMerger.create(group);
     this.instanceInitializerMergers =
-        InstanceInitializerMergerCollection.create(
-            appView, classIdentifiers, codeProvider, group, lensBuilder);
+        InstanceInitializerMergerCollection.create(appView, classIdentifiers, group, lensBuilder);
     this.virtualMethodMergers = virtualMethodMergers;
 
     buildClassIdentifierMap();
@@ -365,14 +363,12 @@
   public static class Builder {
 
     private final AppView<?> appView;
-    private final IRCodeProvider codeProvider;
     private final HorizontalMergeGroup group;
 
     private List<VirtualMethodMerger> virtualMethodMergers;
 
-    public Builder(AppView<?> appView, IRCodeProvider codeProvider, HorizontalMergeGroup group) {
+    public Builder(AppView<?> appView, HorizontalMergeGroup group) {
       this.appView = appView;
-      this.codeProvider = codeProvider;
       this.group = group;
     }
 
@@ -464,7 +460,7 @@
 
     public ClassMerger build(
         HorizontalClassMergerGraphLens.Builder lensBuilder) {
-      return new ClassMerger(appView, codeProvider, lensBuilder, group, virtualMethodMergers);
+      return new ClassMerger(appView, lensBuilder, group, virtualMethodMergers);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 9307f89..51c1aca 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -20,8 +20,6 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
 import com.android.tools.r8.ir.conversion.LirConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.IdentifierMinifier;
 import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
@@ -72,11 +70,7 @@
       throws ExecutionException {
     timing.begin("HorizontalClassMerger");
     if (shouldRun()) {
-      IRCodeProvider codeProvider =
-          appView.hasClassHierarchy()
-              ? IRCodeProvider.create(appView.withClassHierarchy(), this::getConversionOptions)
-              : IRCodeProvider.createThrowing();
-      run(runtimeTypeCheckInfo, codeProvider, executorService, timing);
+      run(runtimeTypeCheckInfo, executorService, timing);
 
       assert ArtProfileCompletenessChecker.verify(appView);
 
@@ -95,13 +89,8 @@
         && !appView.hasCfByteCodePassThroughMethods();
   }
 
-  private MutableMethodConversionOptions getConversionOptions() {
-    return MethodConversionOptions.forLirPhase(appView);
-  }
-
   private void run(
       RuntimeTypeCheckInfo runtimeTypeCheckInfo,
-      IRCodeProvider codeProvider,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
@@ -128,7 +117,7 @@
         new HorizontalClassMergerGraphLens.Builder();
 
     // Determine which classes need a class id field.
-    List<ClassMerger.Builder> classMergerBuilders = createClassMergerBuilders(codeProvider, groups);
+    List<ClassMerger.Builder> classMergerBuilders = createClassMergerBuilders(groups);
     initializeClassIdFields(classMergerBuilders);
 
     // Ensure that all allocations of classes that end up needing a class id use a constructor on
@@ -141,7 +130,7 @@
     ProfileCollectionAdditions profileCollectionAdditions =
         ProfileCollectionAdditions.create(appView);
     SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder =
-        SyntheticInitializerConverter.builder(appView, codeProvider);
+        SyntheticInitializerConverter.builder(appView);
     List<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfos = new ArrayList<>();
     PrunedItems prunedItems =
         applyClassMergers(
@@ -184,7 +173,6 @@
 
     // Set the new graph lens before finalizing any synthetic code.
     appView.setGraphLens(horizontalClassMergerGraphLens);
-    codeProvider.setGraphLens(horizontalClassMergerGraphLens);
 
     // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
     // sites, fields accesses, etc. are correctly transferred to the target classes.
@@ -378,13 +366,13 @@
   }
 
   private List<ClassMerger.Builder> createClassMergerBuilders(
-      IRCodeProvider codeProvider, Collection<HorizontalMergeGroup> groups) {
+      Collection<HorizontalMergeGroup> groups) {
     List<ClassMerger.Builder> classMergerBuilders = new ArrayList<>(groups.size());
     for (HorizontalMergeGroup group : groups) {
       assert group.isNonTrivial();
       assert group.hasInstanceFieldMap();
       assert group.hasTarget();
-      classMergerBuilders.add(new ClassMerger.Builder(appView, codeProvider, group));
+      classMergerBuilders.add(new ClassMerger.Builder(appView, group));
     }
     return classMergerBuilders;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
deleted file mode 100644
index 95c4020..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import java.util.function.Supplier;
-
-public interface IRCodeProvider {
-
-  IRCode buildIR(ProgramMethod method);
-
-  void setGraphLens(GraphLens graphLens);
-
-  static IRCodeProvider create(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Supplier<MutableMethodConversionOptions> getConversionOptions) {
-    return new IRCodeProviderImpl(appView, getConversionOptions);
-  }
-
-  static IRCodeProvider createThrowing() {
-    return new IRCodeProvider() {
-      @Override
-      public IRCode buildIR(ProgramMethod method) {
-        throw new UnsupportedOperationException("Should never build IR for methods in D8");
-      }
-
-      @Override
-      public void setGraphLens(GraphLens graphLens) {}
-    };
-  }
-
-  class IRCodeProviderImpl implements IRCodeProvider {
-
-    private final AppView<AppInfo> appViewForConversion;
-    private Supplier<MutableMethodConversionOptions> getConversionOptions;
-
-    private IRCodeProviderImpl(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
-        Supplier<MutableMethodConversionOptions> getConversionOptions) {
-      // At this point the code rewritings described by repackaging and synthetic finalization have
-      // not been applied to the code objects. These code rewritings will be applied in the
-      // application writer. We therefore simulate that we are in D8, to allow building IR for each
-      // of the class initializers without applying the unapplied code rewritings, to avoid that we
-      // apply the lens more than once to the same piece of code.
-      AppView<AppInfo> appViewForConversion =
-          AppView.createForSimulatingD8InR8(
-              AppInfo.createInitialAppInfo(
-                  appView.appInfo().app(), GlobalSyntheticsStrategy.forNonSynthesizing()));
-      appViewForConversion.setGraphLens(appView.graphLens());
-      appViewForConversion.setCodeLens(appView.codeLens());
-      this.appViewForConversion = appViewForConversion;
-      this.getConversionOptions = getConversionOptions;
-    }
-
-    @Override
-    public IRCode buildIR(ProgramMethod method) {
-      return method
-          .getDefinition()
-          .getCode()
-          .buildIR(method, appViewForConversion, getConversionOptions.get());
-    }
-
-    @Override
-    public void setGraphLens(GraphLens graphLens) {
-      appViewForConversion.setGraphLens(graphLens);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 96aeb10..c7dfba3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Iterables;
@@ -39,7 +40,6 @@
 
   public static InstanceInitializerDescription analyze(
       AppView<? extends AppInfoWithClassHierarchy> appView,
-      IRCodeProvider codeProvider,
       HorizontalMergeGroup group,
       InstanceInitializer instanceInitializer) {
     if (instanceInitializer.isAbsent()) {
@@ -61,19 +61,17 @@
       builder.addInvokeConstructor(invokedConstructor, invokedConstructorArguments);
       return builder.build();
     } else {
-      return analyze(appView, codeProvider, group, instanceInitializer.asPresent().getMethod());
+      return analyze(appView, group, instanceInitializer.asPresent().getMethod());
     }
   }
 
-  @SuppressWarnings("ReferenceEquality")
   public static InstanceInitializerDescription analyze(
       AppView<? extends AppInfoWithClassHierarchy> appView,
-      IRCodeProvider codeProvider,
       HorizontalMergeGroup group,
       ProgramMethod instanceInitializer) {
     InstanceInitializerDescription.Builder builder =
         InstanceInitializerDescription.builder(appView, instanceInitializer);
-    IRCode code = codeProvider.buildIR(instanceInitializer);
+    IRCode code = instanceInitializer.buildIR(appView, MethodConversionOptions.nonConverting());
     GraphLens codeLens = instanceInitializer.getDefinition().getCode().getCodeLens(appView);
     WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(code.entryBlock());
     while (workList.hasNext()) {
@@ -108,8 +106,9 @@
               DexField fieldReference = instancePut.getField();
               DexField lensRewrittenFieldReference =
                   appView.graphLens().lookupField(fieldReference, codeLens);
-              if (lensRewrittenFieldReference.getHolderType()
-                  != instanceInitializer.getHolderType()) {
+              if (lensRewrittenFieldReference
+                  .getHolderType()
+                  .isNotIdenticalTo(instanceInitializer.getHolderType())) {
                 return invalid();
               }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 256971c..be78b39 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -101,16 +101,12 @@
     DexType[] newParameters =
         new DexType[representative.getParameters().size() + BooleanUtils.intValue(needsClassId)];
     System.arraycopy(oldParameters, 0, newParameters, 0, oldParameters.length);
-    for (int i = 0; i < oldParameters.length; i++) {
-      final int parameterIndex = i;
-      Set<DexType> parameterTypes =
-          SetUtils.newIdentityHashSet(
-              builder ->
-                  instanceInitializers.forEach(
-                      instanceInitializer ->
-                          builder.accept(instanceInitializer.getParameter(parameterIndex))));
+    for (int parameterIndex = 0; parameterIndex < oldParameters.length; parameterIndex++) {
+      Set<DexType> parameterTypes = getParameterTypes(instanceInitializers, parameterIndex);
       if (parameterTypes.size() > 1) {
-        newParameters[i] = DexTypeUtils.computeLeastUpperBound(appView, parameterTypes);
+        DexType leastUpperBound = DexTypeUtils.computeLeastUpperBound(appView, parameterTypes);
+        assert DexTypeUtils.isApiSafe(appView, leastUpperBound);
+        newParameters[parameterIndex] = leastUpperBound;
       }
     }
     if (needsClassId) {
@@ -120,6 +116,15 @@
     return dexItemFactory.createInstanceInitializer(group.getTarget().getType(), newParameters);
   }
 
+  private static Set<DexType> getParameterTypes(
+      List<ProgramMethod> instanceInitializers, int parameterIndex) {
+    return SetUtils.newIdentityHashSet(
+        builder ->
+            instanceInitializers.forEach(
+                instanceInitializer ->
+                    builder.accept(instanceInitializer.getParameter(parameterIndex))));
+  }
+
   /**
    * Returns a special original method signature for the synthesized constructor that did not exist
    * prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
@@ -169,9 +174,11 @@
       createNewGroup();
     }
 
-    private void createNewGroup() {
+    private List<ProgramMethod> createNewGroup() {
       estimatedDexCodeSize = 0;
-      instanceInitializerGroups.add(new ArrayList<>());
+      List<ProgramMethod> newGroup = new ArrayList<>();
+      instanceInitializerGroups.add(newGroup);
+      return newGroup;
     }
 
     public Builder add(ProgramMethod instanceInitializer) {
@@ -190,10 +197,40 @@
     }
 
     public Builder addEquivalent(ProgramMethod instanceInitializer) {
-      ListUtils.last(instanceInitializerGroups).add(instanceInitializer);
+      // If adding the given constructor to the current merge group leads to any API unsafe
+      // parameter types, then the constructor should be merged into a new group.
+      List<ProgramMethod> eligibleGroup = null;
+      for (List<ProgramMethod> candidateGroup : instanceInitializerGroups) {
+        if (isMergeApiSafe(candidateGroup, instanceInitializer)) {
+          eligibleGroup = candidateGroup;
+          break;
+        }
+      }
+      if (eligibleGroup == null) {
+        eligibleGroup = createNewGroup();
+      }
+      eligibleGroup.add(instanceInitializer);
       return this;
     }
 
+    private boolean isMergeApiSafe(List<ProgramMethod> group, ProgramMethod instanceInitializer) {
+      if (group.isEmpty()) {
+        return true;
+      }
+      for (int parameterIndex = 0;
+          parameterIndex < instanceInitializer.getParameters().size();
+          parameterIndex++) {
+        Set<DexType> parameterTypes = getParameterTypes(group, parameterIndex);
+        // Adding the given instance initializer to the group can only lead to an API unsafe
+        // parameter type if the instance initializer contributes a new parameter type to the group.
+        if (parameterTypes.add(instanceInitializer.getParameter(parameterIndex))
+            && !DexTypeUtils.isLeastUpperBoundApiSafe(appView, parameterTypes)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
     public List<InstanceInitializerMerger> build(HorizontalMergeGroup group) {
       assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
       return ListUtils.map(
@@ -203,18 +240,19 @@
                   appView, classIdentifiers, group, instanceInitializers, lensBuilder));
     }
 
-    public InstanceInitializerMerger buildSingle(
+    public List<InstanceInitializerMerger> buildEquivalent(
         HorizontalMergeGroup group, InstanceInitializerDescription instanceInitializerDescription) {
       assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
-      assert instanceInitializerGroups.size() == 1;
-      List<ProgramMethod> instanceInitializers = ListUtils.first(instanceInitializerGroups);
-      return new InstanceInitializerMerger(
-          appView,
-          classIdentifiers,
-          group,
-          instanceInitializers,
-          lensBuilder,
-          instanceInitializerDescription);
+      return ListUtils.map(
+          instanceInitializerGroups,
+          instanceInitializers ->
+              new InstanceInitializerMerger(
+                  appView,
+                  classIdentifiers,
+                  group,
+                  instanceInitializers,
+                  lensBuilder,
+                  instanceInitializerDescription));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index e0088ec..5b9e401 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -40,7 +40,6 @@
   public static InstanceInitializerMergerCollection create(
       AppView<?> appView,
       Reference2IntMap<DexType> classIdentifiers,
-      IRCodeProvider codeProvider,
       HorizontalMergeGroup group,
       HorizontalClassMergerGraphLens.Builder lensBuilder) {
     if (!appView.hasClassHierarchy()) {
@@ -61,7 +60,7 @@
                 instanceInitializer -> {
                   InstanceInitializerDescription description =
                       InstanceInitializerAnalysis.analyze(
-                          appViewWithClassHierarchy, codeProvider, group, instanceInitializer);
+                          appViewWithClassHierarchy, group, instanceInitializer);
                   if (description != null) {
                     buildersByDescription
                         .computeIfAbsent(
@@ -80,14 +79,17 @@
         equivalentInstanceInitializerMergers = new LinkedHashMap<>();
     buildersByDescription.forEach(
         (description, builder) -> {
-          InstanceInitializerMerger instanceInitializerMerger =
-              builder.buildSingle(group, description);
-          if (instanceInitializerMerger.size() == 1) {
-            // If there is only one constructor with a specific behavior, then consider it for
-            // normal instance initializer merging below.
-            buildersWithoutDescription.addAll(instanceInitializerMerger.getInstanceInitializers());
-          } else {
-            equivalentInstanceInitializerMergers.put(description, instanceInitializerMerger);
+          List<InstanceInitializerMerger> instanceInitializerMergers =
+              builder.buildEquivalent(group, description);
+          for (InstanceInitializerMerger instanceInitializerMerger : instanceInitializerMergers) {
+            if (instanceInitializerMerger.size() == 1) {
+              // If there is only one constructor with a specific behavior, then consider it for
+              // normal instance initializer merging below.
+              buildersWithoutDescription.addAll(
+                  instanceInitializerMerger.getInstanceInitializers());
+            } else {
+              equivalentInstanceInitializerMergers.put(description, instanceInitializerMerger);
+            }
           }
         });
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index 55a073a..85ad211 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -4,14 +4,12 @@
 
 package com.android.tools.r8.horizontalclassmerging.code;
 
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.util.ArrayList;
@@ -26,25 +24,22 @@
 public class SyntheticInitializerConverter {
 
   private final AppView<?> appView;
-  private final IRCodeProvider codeProvider;
 
   private final List<ProgramMethod> classInitializers;
 
-  private SyntheticInitializerConverter(
-      AppView<?> appView, IRCodeProvider codeProvider, List<ProgramMethod> classInitializers) {
+  private SyntheticInitializerConverter(AppView<?> appView, List<ProgramMethod> classInitializers) {
     this.appView = appView;
-    this.codeProvider = codeProvider;
     this.classInitializers = classInitializers;
   }
 
-  public static Builder builder(AppView<?> appView, IRCodeProvider codeProvider) {
-    return new Builder(appView, codeProvider);
+  public static Builder builder(AppView<?> appView) {
+    return new Builder(appView);
   }
 
   public void convertClassInitializers(ExecutorService executorService) throws ExecutionException {
     if (!classInitializers.isEmpty()) {
       assert appView.dexItemFactory().verifyNoCachedTypeElements();
-      IRConverter converter = new IRConverter(createAppViewForConversion());
+      IRConverter converter = new IRConverter(appView);
       ThreadUtils.processItems(
           classInitializers,
           method -> processMethod(method, converter),
@@ -54,30 +49,8 @@
     }
   }
 
-  private AppView<AppInfo> createAppViewForConversion() {
-    assert appView.enableWholeProgramOptimizations();
-    assert appView.hasClassHierarchy();
-
-    // At this point the code rewritings described by repackaging and synthetic finalization have
-    // not been applied to the code objects. These code rewritings will be applied in the
-    // application writer. We therefore simulate that we are in D8, to allow building IR for each of
-    // the class initializers without applying the unapplied code rewritings, to avoid that we apply
-    // the lens more than once to the same piece of code.
-
-    // Since we are now running in D8 mode clear type elements cache.
-    appView.dexItemFactory().clearTypeElementsCache();
-
-    AppView<AppInfo> appViewForConversion =
-        AppView.createForSimulatingD8InR8(
-            AppInfo.createInitialAppInfo(
-                appView.appInfo().app(), GlobalSyntheticsStrategy.forNonSynthesizing()));
-    appViewForConversion.setGraphLens(appView.graphLens());
-    appViewForConversion.setCodeLens(appView.codeLens());
-    return appViewForConversion;
-  }
-
   private void processMethod(ProgramMethod method, IRConverter converter) {
-    IRCode code = codeProvider.buildIR(method);
+    IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
     converter.removeDeadCodeAndFinalizeIR(
         code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
@@ -89,13 +62,11 @@
   public static class Builder {
 
     private final AppView<?> appView;
-    private final IRCodeProvider codeProvider;
 
     private final List<ProgramMethod> classInitializers = new ArrayList<>();
 
-    private Builder(AppView<?> appView, IRCodeProvider codeProvider) {
+    private Builder(AppView<?> appView) {
       this.appView = appView;
-      this.codeProvider = codeProvider;
     }
 
     public Builder addClassInitializer(ProgramMethod method) {
@@ -104,7 +75,7 @@
     }
 
     public SyntheticInitializerConverter build() {
-      return new SyntheticInitializerConverter(appView, codeProvider, classInitializers);
+      return new SyntheticInitializerConverter(appView, classInitializers);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9d6a8b6..82f47fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -65,7 +65,6 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.D8MemberValuePropagation;
@@ -330,22 +329,6 @@
     return onWaveDoneActions != null;
   }
 
-  public void processSimpleSynthesizeMethods(
-      List<ProgramMethod> methods, ExecutorService executorService) throws ExecutionException {
-    ThreadUtils.processItems(
-        methods,
-        this::processAndFinalizeSimpleSynthesizedMethod,
-        options.getThreadingModule(),
-        executorService);
-  }
-
-  private void processAndFinalizeSimpleSynthesizedMethod(ProgramMethod method) {
-    IRCode code = method.buildIR(appView);
-    assert code != null;
-    new MoveResultRewriter(appView).run(code, Timing.empty());
-    removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
-  }
-
   /**
    * This will replace the Dex code in the method with the Dex code generated from the provided IR.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java
new file mode 100644
index 0000000..2862449
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopDescriptor.java
@@ -0,0 +1,328 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion.passes;
+
+import static com.android.tools.r8.utils.BitUtils.ALL_BITS_SET_MASK;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.Add;
+import com.android.tools.r8.ir.code.And;
+import com.android.tools.r8.ir.code.Binop;
+import com.android.tools.r8.ir.code.Mul;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Or;
+import com.android.tools.r8.ir.code.Shl;
+import com.android.tools.r8.ir.code.Shr;
+import com.android.tools.r8.ir.code.Sub;
+import com.android.tools.r8.ir.code.Ushr;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.Xor;
+
+/**
+ * A Binop descriptor describes left and right identity and absorbing element of binop. <code>
+ *  In a space K, for a binop *:
+ * - i is left identity if for each x in K, i * x = x.
+ * - i is right identity if for each x in K, x * i = x.
+ * - a is left absorbing if for each x in K, a * x = a.
+ * - a is right absorbing if for each x in K, x * a = a.
+ * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z).
+ * </code>
+ */
+enum BinopDescriptor {
+  ADD(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Add.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left + right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left + right;
+    }
+  },
+  SUB(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Sub(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left - right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left - right;
+    }
+  },
+  MUL(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Mul.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 1;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 1;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left * right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left * right;
+    }
+  },
+  // The following two can be improved if we handle ZeroDivide.
+  DIV(false) {
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 1;
+    }
+  },
+  REM(false),
+  AND(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return And.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left & right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left & right;
+    }
+  },
+  OR(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Or.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    Integer rightAbsorbing(boolean isBooleanValue) {
+      return allBitsSet(isBooleanValue);
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left | right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left | right;
+    }
+  },
+  XOR(true) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return Xor.create(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer leftIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    int evaluate(int left, int right) {
+      return left ^ right;
+    }
+
+    @Override
+    long evaluate(long left, long right) {
+      return left ^ right;
+    }
+  },
+  SHL(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Shl(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    boolean isShift() {
+      return true;
+    }
+  },
+  SHR(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Shr(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    boolean isShift() {
+      return true;
+    }
+  },
+  USHR(false) {
+    @Override
+    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+      return new Ushr(numericType, dest, left, right);
+    }
+
+    @Override
+    Integer rightIdentity(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    Integer leftAbsorbing(boolean isBooleanValue) {
+      return 0;
+    }
+
+    @Override
+    boolean isShift() {
+      return true;
+    }
+  };
+
+  final boolean associativeAndCommutative;
+
+  BinopDescriptor(boolean associativeAndCommutative) {
+    this.associativeAndCommutative = associativeAndCommutative;
+  }
+
+  Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
+    throw new Unreachable();
+  }
+
+  Integer allBitsSet(boolean isBooleanValue) {
+    return isBooleanValue ? 1 : ALL_BITS_SET_MASK;
+  }
+
+  Integer leftIdentity(boolean isBooleanValue) {
+    return null;
+  }
+
+  Integer rightIdentity(boolean isBooleanValue) {
+    return null;
+  }
+
+  Integer leftAbsorbing(boolean isBooleanValue) {
+    return null;
+  }
+
+  Integer rightAbsorbing(boolean isBooleanValue) {
+    return null;
+  }
+
+  int evaluate(int left, int right) {
+    throw new Unreachable();
+  }
+
+  long evaluate(long left, long right) {
+    throw new Unreachable();
+  }
+
+  boolean isShift() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
index 160335c..7a480d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.conversion.passes;
 
-import static com.android.tools.r8.utils.BitUtils.ALL_BITS_SET_MASK;
+import static com.android.tools.r8.ir.conversion.passes.BinopDescriptor.ADD;
+import static com.android.tools.r8.ir.conversion.passes.BinopDescriptor.SUB;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -18,10 +18,12 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.LogicalBinop;
 import com.android.tools.r8.ir.code.Mul;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Or;
 import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Shl;
 import com.android.tools.r8.ir.code.Shr;
@@ -45,8 +47,8 @@
 
   private Map<Class<?>, BinopDescriptor> createBinopDescriptors() {
     ImmutableMap.Builder<Class<?>, BinopDescriptor> builder = ImmutableMap.builder();
-    builder.put(Add.class, BinopDescriptor.ADD);
-    builder.put(Sub.class, BinopDescriptor.SUB);
+    builder.put(Add.class, ADD);
+    builder.put(Sub.class, SUB);
     builder.put(Mul.class, BinopDescriptor.MUL);
     builder.put(Div.class, BinopDescriptor.DIV);
     builder.put(Rem.class, BinopDescriptor.REM);
@@ -59,186 +61,6 @@
     return builder.build();
   }
 
-  /**
-   * A Binop descriptor describes left and right identity and absorbing element of binop. <code>
-   * In a space K, for a binop *:
-   * - i is left identity if for each x in K, i * x = x.
-   * - i is right identity if for each x in K, x * i = x.
-   * - a is left absorbing if for each x in K, a * x = a.
-   * - a is right absorbing if for each x in K, x * a = a.
-   * In a space K, a binop * is associative if for each x,y,z in K, (x * y) * z = x * (y * z).
-   * </code>
-   */
-  private enum BinopDescriptor {
-    ADD(0, 0, null, null, true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Add.create(numericType, dest, left, right);
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left + right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left + right;
-      }
-    },
-    SUB(null, 0, null, null, false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Sub(numericType, dest, left, right);
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left - right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left - right;
-      }
-    },
-    MUL(1, 1, 0, 0, true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Mul.create(numericType, dest, left, right);
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left * right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left * right;
-      }
-    },
-    // The following two can be improved if we handle ZeroDivide.
-    DIV(null, 1, null, null, false),
-    REM(null, null, null, null, false),
-    AND(ALL_BITS_SET_MASK, ALL_BITS_SET_MASK, 0, 0, true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return And.create(numericType, dest, left, right);
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left & right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left & right;
-      }
-    },
-    OR(0, 0, ALL_BITS_SET_MASK, ALL_BITS_SET_MASK, true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Or.create(numericType, dest, left, right);
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left | right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left | right;
-      }
-    },
-    XOR(0, 0, null, null, true) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return Xor.create(numericType, dest, left, right);
-      }
-
-      @Override
-      int evaluate(int left, int right) {
-        return left ^ right;
-      }
-
-      @Override
-      long evaluate(long left, long right) {
-        return left ^ right;
-      }
-    },
-    SHL(null, 0, 0, null, false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Shl(numericType, dest, left, right);
-      }
-
-      @Override
-      boolean isShift() {
-        return true;
-      }
-    },
-    SHR(null, 0, 0, null, false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Shr(numericType, dest, left, right);
-      }
-
-      @Override
-      boolean isShift() {
-        return true;
-      }
-    },
-    USHR(null, 0, 0, null, false) {
-      @Override
-      Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-        return new Ushr(numericType, dest, left, right);
-      }
-
-      @Override
-      boolean isShift() {
-        return true;
-      }
-    };
-
-    final Integer leftIdentity;
-    final Integer rightIdentity;
-    final Integer leftAbsorbing;
-    final Integer rightAbsorbing;
-    final boolean associativeAndCommutative;
-
-    BinopDescriptor(
-        Integer leftIdentity,
-        Integer rightIdentity,
-        Integer leftAbsorbing,
-        Integer rightAbsorbing,
-        boolean associativeAndCommutative) {
-      this.leftIdentity = leftIdentity;
-      this.rightIdentity = rightIdentity;
-      this.leftAbsorbing = leftAbsorbing;
-      this.rightAbsorbing = rightAbsorbing;
-      this.associativeAndCommutative = associativeAndCommutative;
-    }
-
-    Binop instantiate(NumericType numericType, Value dest, Value left, Value right) {
-      throw new Unreachable();
-    }
-
-    int evaluate(int left, int right) {
-      throw new Unreachable();
-    }
-
-    long evaluate(long left, long right) {
-      throw new Unreachable();
-    }
-
-    boolean isShift() {
-      return false;
-    }
-  }
-
   @Override
   protected String getRewriterId() {
     return "BinopRewriter";
@@ -263,7 +85,7 @@
             || binop.getNumericType() == NumericType.LONG) {
           BinopDescriptor binopDescriptor = descriptors.get(binop.getClass());
           assert binopDescriptor != null;
-          if (identityAbsorbingSimplification(iterator, binop, binopDescriptor)) {
+          if (identityAbsorbingSimplification(iterator, binop, binopDescriptor, code)) {
             hasChanged = true;
             continue;
           }
@@ -287,7 +109,7 @@
     ConstNumber constBRight = getConstNumber(binop.rightValue());
     if ((constBLeft != null && constBRight != null)
         || (constBLeft == null && constBRight == null)) {
-      return false;
+      return successiveLogicalSimplificationNoConstant(iterator, binop, binopDescriptor, code);
     }
     Value otherValue = constBLeft == null ? binop.leftValue() : binop.rightValue();
     if (otherValue.isPhi() || !otherValue.getDefinition().isBinop()) {
@@ -308,14 +130,14 @@
       if (binopDescriptor.associativeAndCommutative) {
         // a * x * b => x * (a * b) where (a * b) is a constant.
         assert binop.isCommutative();
-        Value newConst = addNewConstNumber(code, iterator, constB, constA, binopDescriptor);
-        replaceBinop(iterator, code, input, newConst, binopDescriptor);
+        rewriteIntoConstThenBinop(
+            iterator, binopDescriptor, binopDescriptor, constB, constA, input, true, code);
         return true;
       } else if (binopDescriptor.isShift()) {
         // x shift: a shift: b => x shift: (a + b) where a + b is a constant.
         if (constBRight != null && constARight != null) {
-          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
-          replaceBinop(iterator, code, input, newConst, binopDescriptor);
+          rewriteIntoConstThenBinop(
+              iterator, ADD, binopDescriptor, constB, constA, input, false, code);
           return true;
         }
       } else if (binop.isSub() && constBRight != null) {
@@ -323,12 +145,10 @@
         // x - a - b => x - (a + b) where (a + b) is a constant.
         // We ignore b - (x - a) and b - (a - x) with constBRight != null.
         if (constARight == null) {
-          Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
-          replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, SUB, SUB, constA, constB, input, true, code);
           return true;
         } else {
-          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
-          replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, ADD, SUB, constB, constA, input, false, code);
           return true;
         }
       }
@@ -337,19 +157,16 @@
         // x + a - b => x + (a - b) where (a - b) is a constant.
         // a + x - b => x + (a - b) where (a - b) is a constant.
         // We ignore b - (x + a) and b - (a + x) with constBRight != null.
-        Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
-        replaceBinop(iterator, code, newConst, input, BinopDescriptor.ADD);
+        rewriteIntoConstThenBinop(iterator, SUB, ADD, constA, constB, input, true, code);
         return true;
       } else if (binop.isAdd() && prevBinop.isSub()) {
         // x - a + b => x - (a - b) where (a - b) is a constant.
         // a - x + b => (a + b) - x where (a + b) is a constant.
         if (constALeft == null) {
-          Value newConst = addNewConstNumber(code, iterator, constA, constB, BinopDescriptor.SUB);
-          replaceBinop(iterator, code, input, newConst, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, SUB, SUB, constA, constB, input, false, code);
           return true;
         } else {
-          Value newConst = addNewConstNumber(code, iterator, constB, constA, BinopDescriptor.ADD);
-          replaceBinop(iterator, code, newConst, input, BinopDescriptor.SUB);
+          rewriteIntoConstThenBinop(iterator, ADD, SUB, constB, constA, input, true, code);
           return true;
         }
       }
@@ -357,6 +174,162 @@
     return false;
   }
 
+  private boolean successiveLogicalSimplificationNoConstant(
+      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
+    if (!(binop.isAnd() || binop.isOr())) {
+      return false;
+    }
+    if (binop.leftValue().isPhi() || binop.rightValue().isPhi()) {
+      return false;
+    }
+    LogicalBinop leftDef = binop.leftValue().getDefinition().asLogicalBinop();
+    LogicalBinop rightDef = binop.rightValue().getDefinition().asLogicalBinop();
+    if (leftDef == null
+        || rightDef == null
+        || (leftDef.getClass() != rightDef.getClass())
+        || (leftDef.getNumericType() != rightDef.getNumericType())) {
+      return false;
+    }
+    // These optimizations were implemented mostly to deal with Compose specific bit patterns.
+    if (leftDef.isAnd() || leftDef.isOr()) {
+      return andOrOnCommonInputSimplification(
+          iterator, binop, leftDef, rightDef, binopDescriptor, code);
+    }
+    if (leftDef.isShl() || leftDef.isShr() || leftDef.isUshr()) {
+      return shiftOnCommonValueSharing(iterator, binop, leftDef, rightDef, binopDescriptor, code);
+    }
+    return false;
+  }
+
+  private boolean andOrOnCommonInputSimplification(
+      InstructionListIterator iterator,
+      Instruction binop,
+      LogicalBinop leftDef,
+      LogicalBinop rightDef,
+      BinopDescriptor binopDescriptor,
+      IRCode code) {
+    // For all permutations of & and |, represented by &| and |&.
+    // (x &| a) |& (x &| b) => (a |& b) &| x.
+    // a |& b will be simplified into a new constant if both constant.
+    Value x, a, b;
+    if (leftDef.leftValue() == rightDef.leftValue()) {
+      x = leftDef.leftValue();
+      a = leftDef.rightValue();
+      b = rightDef.rightValue();
+    } else if (leftDef.leftValue() == rightDef.rightValue()) {
+      x = leftDef.leftValue();
+      a = leftDef.rightValue();
+      b = rightDef.leftValue();
+    } else if (leftDef.rightValue() == rightDef.leftValue()) {
+      x = leftDef.rightValue();
+      a = leftDef.leftValue();
+      b = rightDef.rightValue();
+    } else if (leftDef.rightValue() == rightDef.rightValue()) {
+      x = leftDef.rightValue();
+      a = leftDef.leftValue();
+      b = rightDef.leftValue();
+    } else {
+      return false;
+    }
+
+    rewriteIntoTwoSuccessiveBinops(
+        iterator,
+        binop.getPosition(),
+        binopDescriptor,
+        descriptors.get(leftDef.getClass()),
+        a,
+        b,
+        x,
+        code);
+    return true;
+  }
+
+  private boolean shiftOnCommonValueSharing(
+      InstructionListIterator iterator,
+      Instruction binop,
+      LogicalBinop leftDef,
+      LogicalBinop rightDef,
+      BinopDescriptor binopDescriptor,
+      IRCode code) {
+    // For all permutations of & and |, represented by &|, and any shift operation.
+    // (x shift: val) &| (y shift: val) => (x &| y) shift: val.
+    // x |& y will be simplified into a new constant if both constant.
+    ConstNumber constLeft = getConstNumber(leftDef.rightValue());
+    if (constLeft != null) {
+      // val is a constant.
+      ConstNumber constRight = getConstNumber(rightDef.rightValue());
+      if (constRight == null) {
+        return false;
+      }
+      if (constRight.getRawValue() != constLeft.getRawValue()) {
+        return false;
+      }
+    } else {
+      // val is not constant.
+      if (leftDef.rightValue() != rightDef.rightValue()) {
+        return false;
+      }
+    }
+
+    rewriteIntoTwoSuccessiveBinops(
+        iterator,
+        binop.getPosition(),
+        binopDescriptor,
+        descriptors.get(leftDef.getClass()),
+        leftDef.leftValue(),
+        rightDef.leftValue(),
+        leftDef.rightValue(),
+        code);
+    return true;
+  }
+
+  private void rewriteIntoTwoSuccessiveBinops(
+      InstructionListIterator iterator,
+      Position position,
+      BinopDescriptor firstBinop,
+      BinopDescriptor secondBinop,
+      Value firstLeft,
+      Value firstRight,
+      Value secondRight,
+      IRCode code) {
+    // This creates something along the lines of:
+    // `(firstLeft firstBinop: firstRight) secondBinop: secondOther`.
+    ConstNumber constA = getConstNumber(firstLeft);
+    if (constA != null) {
+      ConstNumber constB = getConstNumber(firstRight);
+      if (constB != null) {
+        rewriteIntoConstThenBinop(
+            iterator, firstBinop, secondBinop, constA, constB, secondRight, true, code);
+        return;
+      }
+    }
+    Binop newFirstBinop = instantiateBinop(code, firstLeft, firstRight, firstBinop);
+    newFirstBinop.setPosition(position);
+    iterator.previous();
+    iterator.add(newFirstBinop);
+    iterator.next();
+    replaceBinop(iterator, code, newFirstBinop.outValue(), secondRight, secondBinop);
+    iterator.previous();
+  }
+
+  private void rewriteIntoConstThenBinop(
+      InstructionListIterator iterator,
+      BinopDescriptor firstBinop,
+      BinopDescriptor secondBinop,
+      ConstNumber firstLeft,
+      ConstNumber firstRight,
+      Value secondOther,
+      boolean newConstFlowsIntoLeft,
+      IRCode code) {
+    Value firstOutValue = insertNewConstNumber(code, iterator, firstLeft, firstRight, firstBinop);
+    replaceBinop(
+        iterator,
+        code,
+        newConstFlowsIntoLeft ? firstOutValue : secondOther,
+        newConstFlowsIntoLeft ? secondOther : firstOutValue,
+        secondBinop);
+  }
+
   private void replaceBinop(
       InstructionListIterator iterator,
       IRCode code,
@@ -365,11 +338,8 @@
       BinopDescriptor binopDescriptor) {
     Binop newBinop = instantiateBinop(code, left, right, binopDescriptor);
     iterator.replaceCurrentInstruction(newBinop);
-    // We need to reset the iterator state after replaceCurrentInstruction so that Iterator#remove()
-    // can work in identityAbsorbingSimplification by calling previous then next.
+    // We need to reset the iterator state to process the new instruction(s).
     iterator.previous();
-    iterator.next();
-    identityAbsorbingSimplification(iterator, newBinop, binopDescriptor);
   }
 
   private Binop instantiateBinop(IRCode code, Value left, Value right, BinopDescriptor descriptor) {
@@ -379,7 +349,7 @@
     return descriptor.instantiate(numericType, newValue, left, right);
   }
 
-  private Value addNewConstNumber(
+  private Value insertNewConstNumber(
       IRCode code,
       InstructionListIterator iterator,
       ConstNumber left,
@@ -400,30 +370,46 @@
   }
 
   private boolean identityAbsorbingSimplification(
-      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor) {
+      InstructionListIterator iterator, Binop binop, BinopDescriptor binopDescriptor, IRCode code) {
     ConstNumber constNumber = getConstNumber(binop.leftValue());
     if (constNumber != null) {
+      boolean isBooleanValue = binop.outValue().knownToBeBoolean();
       if (simplify(
           binop,
           iterator,
           constNumber,
-          binopDescriptor.leftIdentity,
+          binopDescriptor.leftIdentity(isBooleanValue),
           binop.rightValue(),
-          binopDescriptor.leftAbsorbing,
+          binopDescriptor.leftAbsorbing(isBooleanValue),
           binop.leftValue())) {
         return true;
       }
     }
     constNumber = getConstNumber(binop.rightValue());
     if (constNumber != null) {
-      return simplify(
+      boolean isBooleanValue = binop.outValue().knownToBeBoolean();
+      if (simplify(
           binop,
           iterator,
           constNumber,
-          binopDescriptor.rightIdentity,
+          binopDescriptor.rightIdentity(isBooleanValue),
           binop.leftValue(),
-          binopDescriptor.rightAbsorbing,
-          binop.rightValue());
+          binopDescriptor.rightAbsorbing(isBooleanValue),
+          binop.rightValue())) {
+        return true;
+      }
+    }
+    if (binop.leftValue() == binop.rightValue()) {
+      if (binop.isXor() || binop.isSub()) {
+        // a ^ a => 0, a - a => 0
+        ConstNumber zero = new ConstNumber(code.createValue(binop.outValue().getType()), 0);
+        iterator.replaceCurrentInstruction(zero);
+      } else if (binop.isAnd() || binop.isOr()) {
+        // a & a => a, a | a => a.
+        binop.outValue().replaceUsers(binop.leftValue());
+        iterator.remove();
+      }
+      return true;
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index d28ac39..97f2d27 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -43,7 +43,8 @@
     if (appView.options().isGeneratingClassFiles()) {
       return NonEmptyCfInstructionDesugaringCollection.createForCfToCfNonDesugar(appView);
     }
-    return NonEmptyCfInstructionDesugaringCollection.createForCfToDexNonDesugar(appView);
+    return NonEmptyCfInstructionDesugaringCollection.createForCfToDexNonDesugar(
+        appView, apiLevelCompute);
   }
 
   public static CfInstructionDesugaringCollection empty() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 485322e..407daa8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -74,6 +74,10 @@
     if (alwaysThrowingInstructionDesugaring != null) {
       desugarings.add(alwaysThrowingInstructionDesugaring);
     }
+    if (appView.options().apiModelingOptions().enableOutliningOfMethods) {
+      assert appView.options().isGeneratingDex();
+      yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
+    }
     if (appView.options().desugarState.isOff()) {
       this.nestBasedAccessDesugaring = null;
       this.recordRewriter = null;
@@ -100,9 +104,6 @@
     if (disableDesugarer != null) {
       desugarings.add(disableDesugarer);
     }
-    if (appView.options().apiModelingOptions().enableOutliningOfMethods) {
-      yieldingDesugarings.add(new ApiInvokeOutlinerDesugaring(appView, apiLevelCompute));
-    }
     if (appView.options().enableTryWithResourcesDesugaring()) {
       desugarings.add(new TwrInstructionDesugaring(appView));
     }
@@ -174,11 +175,12 @@
     return desugaringCollection;
   }
 
-  static NonEmptyCfInstructionDesugaringCollection createForCfToDexNonDesugar(AppView<?> appView) {
+  static NonEmptyCfInstructionDesugaringCollection createForCfToDexNonDesugar(
+      AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
     assert appView.options().desugarState.isOff();
     assert appView.options().isGeneratingDex();
     NonEmptyCfInstructionDesugaringCollection desugaringCollection =
-        new NonEmptyCfInstructionDesugaringCollection(appView, noAndroidApiLevelCompute());
+        new NonEmptyCfInstructionDesugaringCollection(appView, apiLevelCompute);
     desugaringCollection.desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     desugaringCollection.yieldingDesugarings.add(
         new UnrepresentableInDexInstructionRemover(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
index e13fb7c..9f9738e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
@@ -24,6 +24,8 @@
 
   public ApiLevelRange(
       AndroidApiLevel apiLevelBelowOrEqual, AndroidApiLevel apiLevelGreaterOrEqual) {
+    assert apiLevelGreaterOrEqual == null
+        || apiLevelBelowOrEqual.isGreaterThanOrEqualTo(apiLevelGreaterOrEqual);
     this.apiLevelBelowOrEqual = apiLevelBelowOrEqual;
     this.apiLevelGreaterOrEqual = apiLevelGreaterOrEqual;
   }
@@ -40,6 +42,15 @@
     return apiLevelGreaterOrEqual != null;
   }
 
+  public boolean overlap(ApiLevelRange other) {
+    AndroidApiLevel start =
+        apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : apiLevelGreaterOrEqual;
+    AndroidApiLevel otherStart =
+        other.apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : other.apiLevelGreaterOrEqual;
+    return start.isLessThanOrEqualTo(other.apiLevelBelowOrEqual)
+        && otherStart.isLessThanOrEqualTo(apiLevelBelowOrEqual);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -74,4 +85,13 @@
     }
     return apiLevelGreaterOrEqual.compareTo(other.apiLevelGreaterOrEqual);
   }
+
+  @Override
+  public String toString() {
+    return "[ "
+        + (apiLevelGreaterOrEqual == null ? "B" : apiLevelGreaterOrEqual)
+        + " ; "
+        + apiLevelBelowOrEqual
+        + " ]";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
index e3a9dff..80ca5b3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.Objects;
 
@@ -35,18 +34,6 @@
     return emulatedMethods;
   }
 
-  public EmulatedInterfaceDescriptor merge(EmulatedInterfaceDescriptor other) {
-    if (!rewrittenType.isIdenticalTo(other.getRewrittenType())) {
-      throw new UnsupportedOperationException(
-          "Emulated interface descriptor can only be merged on the same rewritten type.");
-    }
-    ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor> builder =
-        ImmutableMap.builder();
-    builder.putAll(getEmulatedMethods());
-    builder.putAll(other.getEmulatedMethods());
-    return new EmulatedInterfaceDescriptor(rewrittenType, builder.build());
-  }
-
   @Override
   public Object[] toJsonStruct(
       MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index f28dae7..d27ba9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -362,8 +362,8 @@
         emulatedVirtualRetargetThroughEmulatedInterface = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod[]> apiGenericTypesConversion =
         ImmutableMap.builder();
-    private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
-        new IdentityHashMap<>();
+    private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
+        ImmutableMap.builder();
     private final LinkedHashMap<DexType, WrapperDescriptor> wrappers = new LinkedHashMap<>();
     private final ImmutableMap.Builder<DexType, DexType> legacyBackport = ImmutableMap.builder();
     private final ImmutableSet.Builder<DexType> dontRetarget = ImmutableSet.builder();
@@ -409,12 +409,8 @@
       nonEmulatedVirtualRetarget.put(src, dest);
     }
 
-    public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor newDescriptor) {
-      assert newDescriptor != null;
-      EmulatedInterfaceDescriptor oldDescriptor = emulatedInterfaces.get(src);
-      EmulatedInterfaceDescriptor mergedDescriptor =
-          oldDescriptor == null ? newDescriptor : newDescriptor.merge(oldDescriptor);
-      emulatedInterfaces.put(src, mergedDescriptor);
+    public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor descriptor) {
+      emulatedInterfaces.put(src, descriptor);
     }
 
     public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
@@ -491,7 +487,7 @@
           emulatedVirtualRetarget.build(),
           emulatedVirtualRetargetThroughEmulatedInterface.build(),
           apiGenericTypesConversion.build(),
-          ImmutableMap.copyOf(emulatedInterfaces),
+          emulatedInterfaces.build(),
           wrappers,
           legacyBackport.build(),
           dontRetarget.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
index 46ec6ba..f15da9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
@@ -56,7 +56,7 @@
 
 public class MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter {
 
-  static final int MACHINE_VERSION_NUMBER = 201;
+  static final int MACHINE_VERSION_NUMBER = 200;
 
   private final DexItemFactory factory;
   private final Map<String, String> packageMap = new TreeMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index c5c7348..843a915 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexApplication;
@@ -30,8 +31,10 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 public class HumanToMachineSpecificationConverter {
@@ -63,6 +66,8 @@
     Map<ApiLevelRange, MachineRewritingFlags> libraryFlags =
         convertRewritingFlagMap(humanSpec.getLibraryFlags(), synthesizedPrefix, true, identifier);
 
+    checkDisjointEmulatedInterfaceFlags(commonFlags, programFlags, libraryFlags);
+
     MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
         new MultiAPILevelMachineDesugaredLibrarySpecification(
             humanSpec.getOrigin(), machineTopLevelFlags, commonFlags, libraryFlags, programFlags);
@@ -70,6 +75,56 @@
     return machineSpec;
   }
 
+  // For backward compatibility, there can be only one emulated interface flag for a given type at
+  // a given API level.
+  private void checkDisjointEmulatedInterfaceFlags(
+      Map<ApiLevelRange, MachineRewritingFlags> commonFlags,
+      Map<ApiLevelRange, MachineRewritingFlags> programFlags,
+      Map<ApiLevelRange, MachineRewritingFlags> libraryFlags) {
+    Set<DexType> commonTypes = emulatedInterfaceTypes(commonFlags);
+    Set<DexType> programTypes = emulatedInterfaceTypes(programFlags);
+    Set<DexType> libraryTypes = emulatedInterfaceTypes(libraryFlags);
+    if (!Sets.intersection(commonTypes, programTypes).isEmpty()
+        || !Sets.intersection(commonTypes, libraryTypes).isEmpty()
+        || !Sets.intersection(libraryTypes, programTypes).isEmpty()) {
+      throw new CompilationError("Cannot have emulated interface split across flag types");
+    }
+    checkEmulatedInterfaceMap(commonFlags);
+    checkEmulatedInterfaceMap(programFlags);
+    checkEmulatedInterfaceMap(libraryFlags);
+  }
+
+  private void checkEmulatedInterfaceMap(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+    Map<DexType, List<ApiLevelRange>> rangesForType = new IdentityHashMap<>();
+    flagMap.forEach(
+        (range, flags) ->
+            flags
+                .getEmulatedInterfaces()
+                .forEach(
+                    (ei, descr) -> {
+                      rangesForType.putIfAbsent(ei, new ArrayList<>());
+                      rangesForType.get(ei).add(range);
+                    }));
+    rangesForType.keySet().removeIf(t -> rangesForType.get(t).size() == 1);
+    rangesForType.forEach(
+        (type, ranges) -> {
+          for (ApiLevelRange range1 : ranges) {
+            for (ApiLevelRange range2 : ranges) {
+              if (!Objects.equals(range1, range2) && range1.overlap(range2)) {
+                throw new CompilationError(
+                    "Unsupported Machine specification for " + type + " " + range1 + " " + range2);
+              }
+            }
+          }
+        });
+  }
+
+  private Set<DexType> emulatedInterfaceTypes(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+    Set<DexType> types = Sets.newIdentityHashSet();
+    flagMap.forEach((range, flags) -> types.addAll(flags.getEmulatedInterfaces().keySet()));
+    return types;
+  }
+
   private Map<ApiLevelRange, MachineRewritingFlags> convertRewritingFlagMap(
       Map<ApiLevelRange, HumanRewritingFlags> libFlags,
       String synthesizedPrefix,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index c1f203a..13a06ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
@@ -453,6 +454,10 @@
           if (instanceGet.instructionMayHaveSideEffects(appView, context)) {
             return false;
           }
+          if (!AndroidApiLevelUtils.isApiSafeForReference(
+              instanceGet.getField().getType(), appView)) {
+            return false;
+          }
           NewInstance newInstance = null;
           if (instanceGet.object().isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
             newInstance = instanceGet.object().getDefinition().asNewInstance();
@@ -476,6 +481,10 @@
           if (staticGet.instructionMayHaveSideEffects(appView, context)) {
             return false;
           }
+          if (!AndroidApiLevelUtils.isApiSafeForReference(
+              staticGet.getField().getType(), appView)) {
+            return false;
+          }
           if (!isReadOfEffectivelyFinalFieldOutsideInitializer(staticGet)
               && !isEffectivelyFinalField(staticGet)) {
             return false;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index ee0ce51..b5b5299 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -65,6 +65,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
@@ -161,8 +162,13 @@
       Collection<DexProgramClass> allClasses = classes;
       classes = new ArrayList<>(allClasses.size());
       for (DexProgramClass clazz : allClasses) {
-        if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+        if (appView.getSyntheticItems().isGlobalSyntheticClassTransitive(clazz)) {
           globalSyntheticClasses.add(clazz);
+          Consumer<DexProgramClass> globalSyntheticCreatedCallback =
+              appView.options().testing.globalSyntheticCreatedCallback;
+          if (globalSyntheticCreatedCallback != null) {
+            globalSyntheticCreatedCallback.accept(clazz);
+          }
         } else {
           classes.add(clazz);
         }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 61fd940..7f56b25 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -427,6 +427,26 @@
     return isGlobalSyntheticClass(clazz.getType());
   }
 
+  public boolean isGlobalSyntheticClassTransitive(DexProgramClass clazz) {
+    // Fast path the common case where the class is not synthetic at all.
+    if (!isSynthetic(clazz)) {
+      return false;
+    }
+    if (isGlobalSyntheticClass(clazz)) {
+      return true;
+    }
+    DexType type = clazz.getType();
+    for (SyntheticReference<?, ?, ?> reference : committed.getItems(type)) {
+      // Only a single context should exist for a globally derived synthetic, so return early.
+      return isGlobalSyntheticClass(reference.getContext().getSynthesizingContextType());
+    }
+    SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
+    if (definition != null) {
+      return isGlobalSyntheticClass(definition.getContext().getSynthesizingContextType());
+    }
+    return false;
+  }
+
   private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
     if (references == null) {
       return false;
@@ -1017,6 +1037,8 @@
     Consumer<DexProgramClass> globalSyntheticCreatedCallback =
         appView.options().testing.globalSyntheticCreatedCallback;
     if (globalSyntheticCreatedCallback != null) {
+      // These are also reported in the writer to ensure transitive classes are reported too.
+      // However, we keep the test reporting here too to fail fast on direct globals.
       globalSyntheticCreatedCallback.accept(globalSynthetic);
     }
     addGlobalContexts(globalSynthetic.getType(), contexts);
@@ -1046,6 +1068,7 @@
     SyntheticKind kind = kindSelector.select(naming);
     DexType type =
         SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), appView);
+
     SyntheticProgramClassBuilder classBuilder =
         new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
     DexProgramClass clazz =
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index b2d2536..05c0b45 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -44,7 +44,7 @@
   Sv2(32),
   T(33),
   U(34),
-  MASTER(35); // API level for master is tentative.
+  MAIN(35); // API level for main is tentative.
 
   // When updating LATEST and a new version goes public, add a new api-versions.xml to third_party
   // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest. Together
@@ -54,7 +54,7 @@
 
   public static final AndroidApiLevel API_DATABASE_LEVEL = LATEST;
 
-  public static final AndroidApiLevel UNKNOWN = MASTER;
+  public static final AndroidApiLevel UNKNOWN = MAIN;
 
   /** Constant used to signify some unknown min api when compiling platform. */
   public static final int ANDROID_PLATFORM_CONSTANT = 10000;
@@ -106,7 +106,7 @@
       case V40:
         return AndroidApiLevel.R;
       case V41:
-        return AndroidApiLevel.MASTER;
+        return AndroidApiLevel.MAIN;
       default:
         throw new Unreachable();
     }
@@ -186,7 +186,7 @@
       case 34:
         return U;
       default:
-        return MASTER;
+        return MAIN;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index f448ee9..0e53313 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -117,6 +117,25 @@
     return apiLevelOfOriginal.max(apiLevel).isLessThanOrEqualTo(options.getMinApiLevel()).isTrue();
   }
 
+  public static boolean isApiSafeForReference(DexType type, AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexType baseType = type.toBaseType(dexItemFactory);
+    if (baseType.isPrimitiveType()) {
+      return true;
+    }
+    DexClass baseClass = appView.definitionFor(baseType);
+    if (baseClass == null) {
+      // This could be a library class that is only available on newer api levels.
+      return false;
+    }
+    if (!baseClass.isLibraryClass()) {
+      // Program and classpath classes are not api level dependent.
+      return true;
+    }
+    LibraryClass baseLibraryClass = baseClass.asLibraryClass();
+    return isApiSafeForReference(baseLibraryClass, appView);
+  }
+
   public static boolean isApiSafeForReference(LibraryDefinition definition, AppView<?> appView) {
     return isApiSafeForReference(
         definition, appView.apiLevelCompute(), appView.options(), appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index f96774a..dc62cf4 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -66,8 +66,8 @@
 
   public static DexVersion getDexVersion(AndroidApiLevel androidApiLevel) {
     switch (androidApiLevel) {
-        // MASTER is an unknown higher api version we therefore choose the highest known version.
-      case MASTER:
+        // MAIN is an unknown higher api version we therefore choose the highest known version.
+      case MAIN:
       case U:
       case T:
       case Sv2:
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
index 1428459..4fd254a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -203,12 +204,28 @@
     public void finished(AppView<?> appView) {
       Map<DexType, Set<DexType>> globalsToContexts =
           appView.getSyntheticItems().getFinalGlobalSyntheticContexts(appView);
+      // The global synthetics generator is generating the world of globals, thus no contexts exist.
+      if (appView.options().tool.equals(Tool.GlobalSyntheticsGenerator)) {
+        assert globalsToContexts.isEmpty();
+        GlobalsFileBuilder builder = new GlobalsFileBuilder(getKind());
+        globalToBytes.forEach(
+            (globalType, globalBytes) -> {
+              builder.addGlobalSynthetic(globalType.toDescriptorString(), globalBytes);
+            });
+        try {
+          clientConsumer.accept(ByteDataView.of(builder.build()), null, appView.reporter());
+        } catch (IOException e) {
+          appView.reporter().error(new ExceptionDiagnostic(e));
+        }
+        clientConsumer.finished(appView.reporter());
+        return;
+      }
+      // Otherwise, there must be at least one context for any global.
       Map<DexType, Set<DexType>> contextToGlobals = new IdentityHashMap<>();
       for (DexType globalType : globalToBytes.keySet()) {
         // It would be good to assert that the global is a synthetic type, but the naming-lens
         // is not applied to SyntheticItems in AppView.
         Set<DexType> contexts = globalsToContexts.get(globalType);
-        // TODO(b/231598779): Contexts should never be null once fixed for records.
         assert contexts != null;
         assert !contexts.isEmpty();
         for (DexType contextType : contexts) {
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index a1bb8a5..a583e5f 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -84,7 +84,9 @@
           mappedPositions);
     }
 
-    assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
+    assert !mappedPositions.isEmpty()
+        || dexCode.instructions.length == 1
+        || !dexCode.hasThrowingInstructions();
     pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
     return mappedPositions;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index 35b4218..7bdbaa8 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -291,6 +291,7 @@
           originalType)) {
         assert appView.options().lineNumberOptimization.isOff()
             || hasAtMostOnePosition(appView, definition)
+            || !hasThrowingInstructions(definition)
             || appView.isCfByteCodePassThrough(definition);
         return this;
       }
@@ -624,6 +625,11 @@
       return code.isDexCode() && code.asDexCode().instructions.length == 1;
     }
 
+    private boolean hasThrowingInstructions(DexEncodedMethod definition) {
+      Code code = definition.getCode();
+      return code.isDexCode() && code.asDexCode().hasThrowingInstructions();
+    }
+
     private ClassNaming.Builder getBuilder() {
       if (builder == null) {
         builder =
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
deleted file mode 100644
index 775a308..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/CachingArchiveClassFileProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.apiusagesample;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.DirectoryClassFileProvider;
-import com.android.tools.r8.ProgramResource;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class CachingArchiveClassFileProvider extends ArchiveClassFileProvider {
-
-  private ConcurrentHashMap<String, ProgramResource> resources = new ConcurrentHashMap<>();
-
-  private CachingArchiveClassFileProvider(Path archive) throws IOException {
-    super(archive);
-  }
-
-  @Override
-  public ProgramResource getProgramResource(String descriptor) {
-    return resources.computeIfAbsent(descriptor, super::getProgramResource);
-  }
-
-  public static ClassFileResourceProvider getProvider(Path entry)
-      throws IOException {
-    if (Files.isRegularFile(entry)) {
-      return new CachingArchiveClassFileProvider(entry);
-    } else if (Files.isDirectory(entry)) {
-      return DirectoryClassFileProvider.fromDirectory(entry);
-    } else {
-      throw new FileNotFoundException(entry.toString());
-    }
-  }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
deleted file mode 100644
index 80a2845..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ /dev/null
@@ -1,628 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.apiusagesample;
-
-import com.android.tools.r8.ArchiveProgramResourceProvider;
-import com.android.tools.r8.AssertionsConfiguration;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DesugarGraphConsumer;
-import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProgramResource;
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-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.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class D8ApiUsageSample {
-
-  private static final Origin origin =
-      new Origin(Origin.root()) {
-        @Override
-        public String part() {
-          return "D8ApiUsageSample";
-        }
-      };
-
-  private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
-
-  /**
-   * Example invocation:
-   *
-   * <pre>
-   *   java -jar d8-api-uses.jar \
-   *     --output path/to/output/dir \
-   *     --min-api minApiLevel \
-   *     --lib path/to/library.jar \
-   *     --classpath path/to/classpath.jar \
-   *     path/to/input{1,2,3}.{jar,class}
-   * </pre>
-   */
-  public static void main(String[] args) {
-    // Parse arguments with the commandline parser to make use of its API.
-    D8Command.Builder cmd = D8Command.parse(args, origin);
-    CompilationMode mode = cmd.getMode();
-    Path temp = cmd.getOutputPath();
-    int minApiLevel = cmd.getMinApiLevel();
-    // The Builder API does not provide access to the concrete paths
-    // (everything is put into providers) so manually parse them here.
-    List<Path> libraries = new ArrayList<>(1);
-    List<Path> classpath = new ArrayList<>(args.length);
-    List<Path> mainDexList = new ArrayList<>(1);
-    List<Path> mainDexRules = new ArrayList<>(1);
-    List<Path> inputs = new ArrayList<>(args.length);
-    for (int i = 0; i < args.length; i++) {
-      if (args[i].equals("--lib")) {
-        libraries.add(Paths.get(args[++i]));
-      } else if (args[i].equals("--classpath")) {
-        classpath.add(Paths.get(args[++i]));
-      } else if (args[i].equals("--main-dex-list")) {
-        mainDexList.add(Paths.get(args[++i]));
-      } else if (args[i].equals("--main-dex-rules")) {
-        mainDexRules.add(Paths.get(args[++i]));
-      } else if (isArchive(args[i]) || isClassFile(args[i])) {
-        inputs.add(Paths.get(args[i]));
-      }
-    }
-    if (!Files.exists(temp) || !Files.isDirectory(temp)) {
-      throw new RuntimeException("Must supply a temp/output directory");
-    }
-    if (inputs.isEmpty()) {
-      throw new RuntimeException("Must supply program inputs");
-    }
-    if (classpath.isEmpty()) {
-      throw new RuntimeException("Must supply classpath inputs");
-    }
-    if (libraries.isEmpty()) {
-      throw new RuntimeException("Must supply library inputs");
-    }
-    if (mainDexList.isEmpty()) {
-      throw new RuntimeException("Must supply main-dex-list inputs");
-    }
-    if (mainDexRules.isEmpty()) {
-      throw new RuntimeException("Must supply main-dex-rules inputs");
-    }
-
-    useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
-    useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
-    useProgramData(minApiLevel, libraries, classpath, inputs);
-    useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
-    useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
-    useMainDexListFiles(minApiLevel, libraries, classpath, inputs, mainDexList);
-    useMainDexClasses(minApiLevel, libraries, classpath, inputs, mainDexList);
-    useMainDexRulesFiles(minApiLevel, libraries, classpath, inputs, mainDexRules);
-    useMainDexRules(minApiLevel, libraries, classpath, inputs, mainDexRules);
-    useAssertionConfig(minApiLevel, libraries, classpath, inputs);
-    useVArgVariants(minApiLevel, libraries, classpath, inputs, mainDexList);
-    incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
-  }
-
-  // Check API support for compiling Java class-files from the file system.
-  private static void useProgramFileList(
-      CompilationMode mode,
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    try {
-      D8.run(
-          D8Command.builder(handler)
-              .setMode(mode)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath)
-              .addProgramFiles(inputs)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  // Check API support for compiling Java class-files from byte content.
-  private static void useProgramData(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    try {
-      D8Command.Builder builder =
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath);
-      for (ClassFileContent classfile : readClassFiles(inputs)) {
-        builder.addClassProgramData(classfile.data, classfile.origin);
-      }
-      for (Path input : inputs) {
-        if (isDexFile(input)) {
-          builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
-        }
-      }
-      D8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  // Check API support for compiling Java class-files from a program provider abstraction.
-  private static void useProgramResourceProvider(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    try {
-      D8Command.Builder builder =
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath);
-      for (Path input : inputs) {
-        if (isArchive(input)) {
-          builder.addProgramResourceProvider(
-              ArchiveProgramResourceProvider.fromArchive(
-                  input, ArchiveProgramResourceProvider::includeClassFileEntries));
-        } else if (isClassFile(input)) {
-          builder.addProgramResourceProvider(
-              new ProgramResourceProvider() {
-                @Override
-                public Collection<ProgramResource> getProgramResources() throws ResourceException {
-                  return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
-                }
-              });
-        } else if (isDexFile(input)) {
-          builder.addProgramResourceProvider(
-              new ProgramResourceProvider() {
-                @Override
-                public Collection<ProgramResource> getProgramResources() throws ResourceException {
-                  return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
-                }
-              });
-        }
-      }
-      D8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void useLibraryAndClasspathProvider(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    try {
-      D8Command.Builder builder =
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addProgramFiles(inputs);
-      for (Path library : libraries) {
-        builder.addLibraryResourceProvider(CachingArchiveClassFileProvider.getProvider(library));
-      }
-      for (Path path : classpath) {
-        builder.addClasspathResourceProvider(CachingArchiveClassFileProvider.getProvider(path));
-      }
-      D8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  private static void useMainDexListFiles(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs,
-      Collection<Path> mainDexList) {
-    try {
-      D8.run(
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath)
-              .addProgramFiles(inputs)
-              .addMainDexListFiles(mainDexList)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void useMainDexClasses(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs,
-      Collection<Path> mainDexList) {
-    try {
-      List<String> mainDexClasses = new ArrayList<>(1);
-      for (Path path : mainDexList) {
-        for (String line : Files.readAllLines(path)) {
-          String entry = line.trim();
-          if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
-            continue;
-          }
-          mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
-        }
-      }
-      D8.run(
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath)
-              .addProgramFiles(inputs)
-              .addMainDexClasses(mainDexClasses)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  private static void useMainDexRulesFiles(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs,
-      Collection<Path> mainDexRules) {
-    try {
-      D8.run(
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath)
-              .addProgramFiles(inputs)
-              .addMainDexRulesFiles(mainDexRules)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void useMainDexRules(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs,
-      Collection<Path> mainDexRulesFiles) {
-    try {
-      D8Command.Builder builder =
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath)
-              .addProgramFiles(inputs);
-      for (Path mainDexRulesFile : mainDexRulesFiles) {
-        builder.addMainDexRules(
-            Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
-      }
-      D8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  private static void useAssertionConfig(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    try {
-      D8.run(
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath)
-              .addProgramFiles(inputs)
-              .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
-              .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopePackage("com.android.tools.apiusagesample")
-                          .setCompileTimeEnable()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopePackage("com.android.tools.apiusagesample")
-                          .setPassthrough()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopePackage("com.android.tools.apiusagesample")
-                          .setCompileTimeDisable()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
-                          .setCompileTimeEnable()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
-                          .setPassthrough()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
-                          .setCompileTimeDisable()
-                          .build())
-              .addAssertionsConfiguration(
-                  AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
-              .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
-              .addAssertionsConfiguration(
-                  AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  // Check API support for all the varg variants.
-  private static void useVArgVariants(
-      int minApiLevel,
-      List<Path> libraries,
-      List<Path> classpath,
-      List<Path> inputs,
-      List<Path> mainDexList) {
-    try {
-      D8.run(
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries.get(0))
-              .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
-              .addClasspathFiles(classpath.get(0))
-              .addClasspathFiles(classpath.stream().skip(1).toArray(Path[]::new))
-              .addProgramFiles(inputs.get(0))
-              .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
-              .addMainDexListFiles(mainDexList.get(0))
-              .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void incrementalCompileAndMerge(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    // Compile and merge via index intermediates.
-    mergeIntermediates(
-        minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
-    // Compile and merge via per-classfile intermediates.
-    mergeIntermediates(
-        minApiLevel, compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
-  }
-
-  private static Collection<byte[]> compileToIndexedIntermediates(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
-    try {
-      D8.run(
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setIntermediate(true)
-              .setProgramConsumer(consumer)
-              .addClasspathFiles(classpath)
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .setDisableDesugaring(false)
-              .setDesugarGraphConsumer(new MyDesugarGraphConsumer())
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-    return consumer.bytes;
-  }
-
-  private static Collection<byte[]> compileToPerClassFileIntermediates(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> classpath,
-      Collection<Path> inputs) {
-    PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
-    try {
-      D8.run(
-          D8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(consumer)
-              .addLibraryFiles(libraries)
-              .addClasspathFiles(classpath)
-              .addProgramFiles(inputs)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-    return consumer.bytes;
-  }
-
-  private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
-    D8Command.Builder builder =
-        D8Command.builder(handler)
-            .setMinApiLevel(minApiLevel)
-            .setProgramConsumer(new EnsureOutputConsumer())
-            .setDisableDesugaring(true);
-    for (byte[] intermediate : intermediates) {
-      builder.addDexProgramData(intermediate, Origin.unknown());
-    }
-    try {
-      D8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected merging error", e);
-    }
-  }
-
-  // Helpers for tests.
-  // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
-  // rely on it.
-
-  private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
-    List<ClassFileContent> classfiles = new ArrayList<>();
-    for (Path file : files) {
-      if (isArchive(file)) {
-        Origin zipOrigin = new PathOrigin(file);
-        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
-        ZipEntry entry;
-        while (null != (entry = zip.getNextEntry())) {
-          String name = entry.getName();
-          if (isClassFile(name)) {
-            Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
-            classfiles.add(new ClassFileContent(origin, readBytes(zip)));
-          }
-        }
-      } else if (isClassFile(file)) {
-        classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
-      }
-    }
-    return classfiles;
-  }
-
-  private static byte[] readBytes(InputStream stream) throws IOException {
-    try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
-      byte[] buffer = new byte[0xffff];
-      for (int length; (length = stream.read(buffer)) != -1; ) {
-        bytes.write(buffer, 0, length);
-      }
-      return bytes.toByteArray();
-    }
-  }
-
-  private static boolean isClassFile(Path file) {
-    return isClassFile(file.toString());
-  }
-
-  private static boolean isClassFile(String file) {
-    file = StringUtils.toLowerCase(file);
-    return file.endsWith(".class");
-  }
-
-  private static boolean isDexFile(Path file) {
-    return isDexFile(file.toString());
-  }
-
-  private static boolean isDexFile(String file) {
-    file = StringUtils.toLowerCase(file);
-    return file.endsWith(".dex");
-  }
-
-  private static boolean isArchive(Path file) {
-    return isArchive(file.toString());
-  }
-
-  private static boolean isArchive(String file) {
-    file = StringUtils.toLowerCase(file);
-    return file.endsWith(".zip") || file.endsWith(".jar");
-  }
-
-  private static class ClassFileContent {
-    final Origin origin;
-    final byte[] data;
-
-    public ClassFileContent(Origin origin, byte[] data) {
-      this.origin = origin;
-      this.data = data;
-    }
-  }
-
-  private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
-
-    List<byte[]> bytes = new ArrayList<>();
-
-    @Override
-    public synchronized void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
-      bytes.add(data);
-    }
-
-    @Override
-    public void finished(DiagnosticsHandler handler) {}
-  }
-
-  private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
-
-    List<byte[]> bytes = new ArrayList<>();
-
-    @Override
-    public synchronized void accept(
-        String primaryClassDescriptor,
-        byte[] data,
-        Set<String> descriptors,
-        DiagnosticsHandler handler) {
-      bytes.add(data);
-    }
-
-    @Override
-    public void finished(DiagnosticsHandler handler) {}
-  }
-
-  private static class EnsureOutputConsumer implements DexIndexedConsumer {
-    boolean hasOutput = false;
-
-    @Override
-    public synchronized void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
-      hasOutput = true;
-    }
-
-    @Override
-    public void finished(DiagnosticsHandler handler) {
-      if (!hasOutput) {
-        handler.error(new StringDiagnostic("Expected to produce output but had none"));
-      }
-    }
-  }
-
-  private static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
-
-    @Override
-    public void accept(Origin dependent, Origin dependency) {
-    }
-
-    public void finished() {
-
-    }
-  }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
deleted file mode 100644
index d523a53..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8DiagnosticsHandler.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.apiusagesample;
-
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.position.TextPosition;
-import com.android.tools.r8.position.TextRange;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-class D8DiagnosticsHandler implements DiagnosticsHandler {
-
-  public D8DiagnosticsHandler() {
-  }
-
-  public static Origin getOrigin(Path root, Path entry) {
-    if (Files.isRegularFile(root)) {
-      return new ArchiveEntryOrigin(entry.toString(), new PathOrigin(root));
-    } else {
-      return new PathOrigin(root.resolve(entry.toString()));
-    }
-  }
-
-  @Override
-  public void error(Diagnostic error) {
-    convertToMessage(error);
-  }
-
-  @Override
-  public void warning(Diagnostic warning) {
-    if (warning instanceof InterfaceDesugarMissingTypeDiagnostic) {
-      desugarInterfaceMethodInfo((InterfaceDesugarMissingTypeDiagnostic) warning);
-    } else {
-      convertToMessage(warning);
-    }
-  }
-
-  @Override
-  public void info(Diagnostic info) {
-    convertToMessage(info);
-  }
-
-  void desugarInterfaceMethodInfo(InterfaceDesugarMissingTypeDiagnostic info) {
-    System.out.println("desugar is missing: " + info.getMissingType().toString());
-    System.out.println("  used from: " + info.getContextType().toString());
-    convertToMessage(info);
-  }
-
-  protected void convertToMessage(Diagnostic diagnostic) {
-    String textMessage = diagnostic.getDiagnosticMessage();
-
-    Origin origin = diagnostic.getOrigin();
-    Position positionInOrigin = diagnostic.getPosition();
-    String position;
-    if (origin instanceof PathOrigin) {
-      Path originFile = ((PathOrigin) origin).getPath();
-      if (positionInOrigin instanceof TextRange) {
-        TextRange textRange = (TextRange) positionInOrigin;
-        position = originFile + ": "
-            + textRange.getStart().getLine() + "," + textRange.getStart().getColumn()
-            + " - " + textRange.getEnd().getLine() + "," + textRange.getEnd().getColumn();
-      } else if (positionInOrigin instanceof TextPosition) {
-        TextPosition textPosition = (TextPosition) positionInOrigin;
-        position = originFile + ": "
-            + textPosition.getLine() + "," + textPosition.getColumn();
-      } else {
-        position = originFile.toString();
-      }
-    } else if (origin.parent() instanceof PathOrigin) {
-      Path originFile = ((PathOrigin) origin.parent()).getPath();
-      position = originFile.toString();
-    } else {
-      position = "UNKNOWN";
-      if (origin != Origin.unknown()) {
-        textMessage = origin.toString() + ": " + textMessage;
-      }
-    }
-
-    System.out.println(position + ": " + textMessage);
-  }
-}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
deleted file mode 100644
index 521ccba..0000000
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ /dev/null
@@ -1,612 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.apiusagesample;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.ArchiveProgramResourceProvider;
-import com.android.tools.r8.AssertionsConfiguration;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.ProgramResource;
-import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.Version;
-import com.android.tools.r8.origin.ArchiveEntryOrigin;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.nio.charset.StandardCharsets;
-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.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class R8ApiUsageSample {
-
-  private static final Origin origin =
-      new Origin(Origin.root()) {
-        @Override
-        public String part() {
-          return "R8ApiUsageSample";
-        }
-      };
-
-  private static final DiagnosticsHandler handler = new D8DiagnosticsHandler();
-
-  /**
-   * Example invocation:
-   *
-   * <pre>
-   *   java -jar r8-api-uses.jar \
-   *     --output path/to/output/dir \
-   *     --min-api minApiLevel \
-   *     --lib path/to/library.jar \
-   *     path/to/input{1,2,3}.{jar,class}
-   * </pre>
-   */
-  public static void main(String[] args) {
-    // Check version API
-    checkVersionApi();
-    // Parse arguments with the commandline parser to make use of its API.
-    R8Command.Builder cmd = R8Command.parse(args, origin);
-    CompilationMode mode = cmd.getMode();
-    Path temp = cmd.getOutputPath();
-    int minApiLevel = cmd.getMinApiLevel();
-    // The Builder API does not provide access to the concrete paths
-    // (everything is put into providers) so manually parse them here.
-    List<Path> libraries = new ArrayList<>(1);
-    List<Path> mainDexList = new ArrayList<>(1);
-    List<Path> mainDexRules = new ArrayList<>(1);
-    List<Path> pgConf = new ArrayList<>(1);
-    List<Path> inputs = new ArrayList<>(args.length);
-    for (int i = 0; i < args.length; i++) {
-      if (args[i].equals("--lib")) {
-        libraries.add(Paths.get(args[++i]));
-      } else if (args[i].equals("--main-dex-list")) {
-        mainDexList.add(Paths.get(args[++i]));
-      } else if (args[i].equals("--main-dex-rules")) {
-        mainDexRules.add(Paths.get(args[++i]));
-      } else if (args[i].equals("--pg-conf")) {
-        pgConf.add(Paths.get(args[++i]));
-      } else if (isArchive(args[i]) || isClassFile(args[i])) {
-        inputs.add(Paths.get(args[i]));
-      }
-    }
-    if (!Files.exists(temp) || !Files.isDirectory(temp)) {
-      throw new RuntimeException("Must supply a temp/output directory");
-    }
-    if (inputs.isEmpty()) {
-      throw new RuntimeException("Must supply program inputs");
-    }
-    if (libraries.isEmpty()) {
-      throw new RuntimeException("Must supply library inputs");
-    }
-    if (mainDexList.isEmpty()) {
-      throw new RuntimeException("Must supply main-dex-list inputs");
-    }
-    if (mainDexRules.isEmpty()) {
-      throw new RuntimeException("Must supply main-dex-rules inputs");
-    }
-    if (pgConf.isEmpty()) {
-      throw new RuntimeException("Must supply pg-conf inputs");
-    }
-
-    useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
-    useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
-    useProgramData(minApiLevel, libraries, inputs);
-    useProgramResourceProvider(minApiLevel, libraries, inputs);
-    useLibraryResourceProvider(minApiLevel, libraries, inputs);
-    useMainDexListFiles(minApiLevel, libraries, inputs, mainDexList);
-    useMainDexClasses(minApiLevel, libraries, inputs, mainDexList);
-    useMainDexRulesFiles(minApiLevel, libraries, inputs, mainDexRules);
-    useMainDexRules(minApiLevel, libraries, inputs, mainDexRules);
-    useProguardConfigFiles(minApiLevel, libraries, inputs, mainDexList, pgConf);
-    useProguardConfigLines(minApiLevel, libraries, inputs, mainDexList, pgConf);
-    useAssertionConfig(minApiLevel, libraries, inputs);
-    useVArgVariants(minApiLevel, libraries, inputs, mainDexList, mainDexRules, pgConf);
-    useProguardConfigConsumers(minApiLevel, libraries, inputs, pgConf);
-  }
-
-  private static class InMemoryStringConsumer implements StringConsumer {
-    public String value = null;
-
-    @Override
-    public void accept(String string, DiagnosticsHandler handler) {
-      value = string;
-    }
-  }
-
-  private static void useProguardConfigConsumers(
-      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
-    InMemoryStringConsumer usageConsumer = new InMemoryStringConsumer();
-    InMemoryStringConsumer seedsConsumer = new InMemoryStringConsumer();
-    InMemoryStringConsumer configConsumer = new InMemoryStringConsumer();
-    try {
-      R8.run(
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .addProguardConfigurationFiles(pgConf)
-              .setProguardUsageConsumer(usageConsumer)
-              .setProguardSeedsConsumer(seedsConsumer)
-              .setProguardConfigurationConsumer(configConsumer)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exception", e);
-    }
-    if (usageConsumer.value == null) {
-      throw new RuntimeException("Expected usage info but had none");
-    }
-    if (seedsConsumer.value == null) {
-      throw new RuntimeException("Expected seeds info but had none");
-    }
-    if (configConsumer.value == null) {
-      throw new RuntimeException("Expected config info but had none");
-    }
-  }
-
-  // Check API support for compiling Java class-files from the file system.
-  private static void useProgramFileList(
-      CompilationMode mode, int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
-    try {
-      R8.run(
-          R8Command.builder(handler)
-              .setMode(mode)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  // Check API support for compiling Java class-files from byte content.
-  private static void useProgramData(
-      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
-    try {
-      R8Command.Builder builder =
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries);
-      for (ClassFileContent classfile : readClassFiles(inputs)) {
-        builder.addClassProgramData(classfile.data, classfile.origin);
-      }
-      R8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  // Check API support for compiling Java class-files from a program provider abstraction.
-  private static void useProgramResourceProvider(
-      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
-    try {
-      R8Command.Builder builder =
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries);
-      for (Path input : inputs) {
-        if (isArchive(input)) {
-          builder.addProgramResourceProvider(
-              ArchiveProgramResourceProvider.fromArchive(
-                  input, ArchiveProgramResourceProvider::includeClassFileEntries));
-        } else {
-          builder.addProgramResourceProvider(
-              new ProgramResourceProvider() {
-                @Override
-                public Collection<ProgramResource> getProgramResources() throws ResourceException {
-                  return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
-                }
-              });
-        }
-      }
-      R8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void useLibraryResourceProvider(
-      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
-    try {
-      R8Command.Builder builder =
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addProgramFiles(inputs);
-      for (Path library : libraries) {
-        builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
-      }
-      R8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  private static void useMainDexListFiles(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> inputs,
-      Collection<Path> mainDexList) {
-    try {
-      R8.run(
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .addMainDexListFiles(mainDexList)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void useMainDexClasses(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> inputs,
-      Collection<Path> mainDexList) {
-    try {
-      List<String> mainDexClasses = new ArrayList<>(1);
-      for (Path path : mainDexList) {
-        for (String line : Files.readAllLines(path)) {
-          String entry = line.trim();
-          if (entry.isEmpty() || entry.startsWith("#") || !entry.endsWith(".class")) {
-            continue;
-          }
-          mainDexClasses.add(entry.replace(".class", "").replace("/", "."));
-        }
-      }
-      R8.run(
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .addMainDexClasses(mainDexClasses)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  private static void useMainDexRulesFiles(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> inputs,
-      Collection<Path> mainDexRules) {
-    try {
-      R8.run(
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .addMainDexRulesFiles(mainDexRules)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void useMainDexRules(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> inputs,
-      Collection<Path> mainDexRulesFiles) {
-    try {
-      R8Command.Builder builder =
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setDisableTreeShaking(true)
-              .setDisableMinification(true)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs);
-      for (Path mainDexRulesFile : mainDexRulesFiles) {
-        builder.addMainDexRules(
-            Files.readAllLines(mainDexRulesFile), new PathOrigin(mainDexRulesFile));
-      }
-      R8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  private static void useProguardConfigFiles(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> inputs,
-      Collection<Path> mainDexList,
-      List<Path> pgConf) {
-    try {
-      R8.run(
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .addMainDexListFiles(mainDexList)
-              .addProguardConfigurationFiles(pgConf)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  private static void useProguardConfigLines(
-      int minApiLevel,
-      Collection<Path> libraries,
-      Collection<Path> inputs,
-      Collection<Path> mainDexList,
-      List<Path> pgConf) {
-    try {
-      R8Command.Builder builder =
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .addMainDexListFiles(mainDexList);
-      for (Path file : pgConf) {
-        builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
-      }
-      R8.run(builder.build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    } catch (IOException e) {
-      throw new RuntimeException("Unexpected IO exception", e);
-    }
-  }
-
-  private static void useAssertionConfig(
-      int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
-    try {
-      R8.run(
-          R8Command.builder(handler)
-              .setDisableTreeShaking(true)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries)
-              .addProgramFiles(inputs)
-              .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
-              .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopePackage("com.android.tools.apiusagesample")
-                          .setCompileTimeEnable()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopePackage("com.android.tools.apiusagesample")
-                          .setPassthrough()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopePackage("com.android.tools.apiusagesample")
-                          .setCompileTimeDisable()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
-                          .setCompileTimeEnable()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
-                          .setPassthrough()
-                          .build())
-              .addAssertionsConfiguration(
-                  b ->
-                      b.setScopeClass("com.android.tools.apiusagesample.D8ApiUsageSample")
-                          .setCompileTimeDisable()
-                          .build())
-              .addAssertionsConfiguration(
-                  AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
-              .addAssertionsConfiguration(AssertionsConfiguration.Builder::passthroughAllAssertions)
-              .addAssertionsConfiguration(
-                  AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  // Check API support for all the varg variants.
-  private static void useVArgVariants(
-      int minApiLevel,
-      List<Path> libraries,
-      List<Path> inputs,
-      List<Path> mainDexList,
-      List<Path> mainDexRules,
-      List<Path> pgConf) {
-    try {
-      R8.run(
-          R8Command.builder(handler)
-              .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(new EnsureOutputConsumer())
-              .addLibraryFiles(libraries.get(0))
-              .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
-              .addProgramFiles(inputs.get(0))
-              .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
-              .addMainDexListFiles(mainDexList.get(0))
-              .addMainDexListFiles(mainDexList.stream().skip(1).toArray(Path[]::new))
-              .addMainDexRulesFiles(mainDexRules.get(0))
-              .addMainDexRulesFiles(mainDexRules.stream().skip(1).toArray(Path[]::new))
-              .addProguardConfigurationFiles(pgConf.get(0))
-              .addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
-              .build());
-    } catch (CompilationFailedException e) {
-      throw new RuntimeException("Unexpected compilation exceptions", e);
-    }
-  }
-
-  // Helpers for tests.
-  // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
-  // rely on it.
-
-  private static List<ClassFileContent> readClassFiles(Collection<Path> files) throws IOException {
-    List<ClassFileContent> classfiles = new ArrayList<>();
-    for (Path file : files) {
-      if (isArchive(file)) {
-        Origin zipOrigin = new PathOrigin(file);
-        ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
-        ZipEntry entry;
-        while (null != (entry = zip.getNextEntry())) {
-          String name = entry.getName();
-          if (isClassFile(name)) {
-            Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
-            classfiles.add(new ClassFileContent(origin, readBytes(zip)));
-          }
-        }
-      } else if (isClassFile(file)) {
-        classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
-      }
-    }
-    return classfiles;
-  }
-
-  private static byte[] readBytes(InputStream stream) throws IOException {
-    try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
-      byte[] buffer = new byte[0xffff];
-      for (int length; (length = stream.read(buffer)) != -1; ) {
-        bytes.write(buffer, 0, length);
-      }
-      return bytes.toByteArray();
-    }
-  }
-
-  private static boolean isClassFile(Path file) {
-    return isClassFile(file.toString());
-  }
-
-  private static boolean isClassFile(String file) {
-    file = StringUtils.toLowerCase(file);
-    return file.endsWith(".class");
-  }
-
-  private static boolean isArchive(Path file) {
-    return isArchive(file.toString());
-  }
-
-  private static boolean isArchive(String file) {
-    file = StringUtils.toLowerCase(file);
-    return file.endsWith(".zip") || file.endsWith(".jar");
-  }
-
-  private static class ClassFileContent {
-    final Origin origin;
-    final byte[] data;
-
-    public ClassFileContent(Origin origin, byte[] data) {
-      this.origin = origin;
-      this.data = data;
-    }
-  }
-
-  private static class EnsureOutputConsumer implements DexIndexedConsumer {
-    boolean hasOutput = false;
-
-    @Override
-    public synchronized void accept(
-        int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
-      hasOutput = true;
-    }
-
-    @Override
-    public void finished(DiagnosticsHandler handler) {
-      if (!hasOutput) {
-        handler.error(new StringDiagnostic("Expected to produce output but had none"));
-      }
-    }
-  }
-
-  private static void checkVersionApi() {
-    String labelValue;
-    int labelAccess;
-    try {
-      Field field = Version.class.getDeclaredField("LABEL");
-      labelAccess = field.getModifiers();
-      labelValue = (String) field.get(Version.class);
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
-    if (!Modifier.isPublic(labelAccess)
-        || !Modifier.isStatic(labelAccess)
-        || !Modifier.isFinal(labelAccess)) {
-      throw new RuntimeException("Expected public static final LABEL");
-    }
-    if (labelValue.isEmpty()) {
-      throw new RuntimeException("Expected LABEL constant");
-    }
-    if (Version.LABEL.isEmpty()) {
-      throw new RuntimeException("Expected LABEL constant");
-    }
-    if (Version.getVersionString() == null) {
-      throw new RuntimeException("Expected getVersionString API");
-    }
-    if (Version.getMajorVersion() < -1) {
-      throw new RuntimeException("Expected getMajorVersion API");
-    }
-    if (Version.getMinorVersion() < -1) {
-      throw new RuntimeException("Expected getMinorVersion API");
-    }
-    if (Version.getPatchVersion() < -1) {
-      throw new RuntimeException("Expected getPatchVersion API");
-    }
-    if (Version.getPreReleaseString() == null && false) {
-      throw new RuntimeException("Expected getPreReleaseString API");
-    }
-    if (Version.isDevelopmentVersion() && false) {
-      throw new RuntimeException("Expected isDevelopmentVersion API");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
deleted file mode 100644
index d660bed..0000000
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class D8ApiBinaryCompatibilityTests extends TestBase {
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public D8ApiBinaryCompatibilityTests(TestParameters parameters) {
-    parameters.assertNoneRuntime();
-  }
-
-  @Test
-  public void testCompatibility() throws IOException {
-    Path jar = ToolHelper.API_SAMPLE_JAR;
-    String main = "com.android.tools.apiusagesample.D8ApiUsageSample";
-    int minApiLevel = AndroidApiLevel.K.getLevel();
-
-    Path lib1 =
-        Paths.get(
-            ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
-            "desugaringwithmissingclasslib1" + JAR_EXTENSION);
-    Path lib2 =
-        Paths.get(
-            ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR,
-            "desugaringwithmissingclasslib2" + JAR_EXTENSION);
-    Path inputDir =
-        Paths.get(
-            ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR, "classes", "desugaringwithmissingclasstest1");
-    List<Path> input =
-        ImmutableList.of(
-            inputDir.resolve("ImplementMethodsWithDefault.class"), inputDir.resolve("Main.class"));
-
-    Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
-    FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
-
-    Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
-    FileUtils.writeTextFile(mainDexRules, "# empty file");
-
-    // It is important to place the api usage sample jar after the current classpath because we want
-    // to find D8/R8 classes before the ones in the jar, otherwise renamed classes and fields cannot
-    // be found.
-    String classPath = System.getProperty("java.class.path") + File.pathSeparator + jar;
-    List<String> command =
-        ImmutableList.<String>builder()
-            .addAll(
-                ImmutableList.of(
-                    ToolHelper.getJavaExecutable(),
-                    "-cp",
-                    classPath,
-                    main,
-                    // Compiler arguments.
-                    "--output",
-                    temp.newFolder().getAbsolutePath(),
-                    "--min-api",
-                    Integer.toString(minApiLevel),
-                    "--main-dex-list",
-                    mainDexList.toString(),
-                    "--main-dex-rules",
-                    mainDexRules.toString(),
-                    "--lib",
-                    ToolHelper.getAndroidJar(AndroidApiLevel.getAndroidApiLevel(minApiLevel))
-                        .toString(),
-                    "--classpath",
-                    lib1.toString(),
-                    "--classpath",
-                    lib2.toString()))
-            .addAll(input.stream().map(Path::toString).collect(Collectors.toList()))
-            .build();
-
-    ProcessBuilder builder = new ProcessBuilder(command);
-    ProcessResult result = ToolHelper.runProcess(builder);
-    assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
-    Assert.assertEquals("", filterOutMainDexListWarnings(result.stdout));
-    Assert.assertEquals("", result.stderr);
-  }
-
-  public static String filterOutMainDexListWarnings(String output) {
-    StringBuilder builder = new StringBuilder();
-    for (String line : output.split("\n")) {
-      if (!line.contains("Unsupported usage of main-dex list")) {
-        builder.append(line).append("\n");
-      }
-    }
-    return builder.toString();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
deleted file mode 100644
index 67753da..0000000
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.FileUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class R8ApiBinaryCompatibilityTests extends TestBase {
-
-  static final Path JAR = ToolHelper.API_SAMPLE_JAR;
-  static final String MAIN = "com.android.tools.apiusagesample.R8ApiUsageSample";
-  static final AndroidApiLevel MIN_API = AndroidApiLevel.K;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public R8ApiBinaryCompatibilityTests(TestParameters parameters) {
-    parameters.assertNoneRuntime();
-  }
-
-  @Test
-  public void testCompatibility() throws IOException {
-    List<Path> inputs =
-        ImmutableList.of(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "arithmetic.jar"));
-
-    String keepMain = "-keep public class arithmetic.Arithmetic {\n"
-        + "  public static void main(java.lang.String[]);\n"
-        + "}";
-
-    Path pgConf = temp.getRoot().toPath().resolve("pg.conf");
-    FileUtils.writeTextFile(pgConf, keepMain);
-
-    Path mainDexRules = temp.getRoot().toPath().resolve("maindex.rules");
-    FileUtils.writeTextFile(mainDexRules, keepMain);
-
-    Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
-    FileUtils.writeTextFile(mainDexList, "arithmetic/Arithmetic.class");
-
-    List<String> command =
-        ImmutableList.<String>builder()
-            .addAll(
-                ImmutableList.of(
-                    ToolHelper.getJavaExecutable(),
-                    "-cp",
-                    JAR.toString() + File.pathSeparator + System.getProperty("java.class.path"),
-                    MAIN,
-                    // Compiler arguments.
-                    "--output",
-                    temp.newFolder().toString(),
-                    "--min-api",
-                    Integer.toString(MIN_API.getLevel()),
-                    "--pg-conf",
-                    pgConf.toString(),
-                    "--main-dex-rules",
-                    mainDexRules.toString(),
-                    "--main-dex-list",
-                    mainDexList.toString(),
-                    "--lib",
-                    ToolHelper.getAndroidJar(MIN_API).toString()))
-            .addAll(inputs.stream().map(Path::toString).collect(Collectors.toList()))
-            .build();
-
-    ProcessBuilder builder = new ProcessBuilder(command);
-    ProcessResult result = ToolHelper.runProcess(builder);
-    assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
-    assertEquals("", D8ApiBinaryCompatibilityTests.filterOutMainDexListWarnings(result.stdout));
-    assertEquals("", result.stderr);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java b/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java
index 6d9c804..0b0b108 100644
--- a/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/AndroidJarMasterTest.java
@@ -30,7 +30,7 @@
   @Test
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MASTER))
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MAIN))
         .addInnerClasses(getClass())
         .setMinApi(parameters)
         .run(parameters.getRuntime(), TestClass.class)
@@ -41,7 +41,7 @@
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
     testForR8(parameters.getBackend())
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MASTER))
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MAIN))
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java
new file mode 100644
index 0000000..c463f1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelConstantCanonicalizationTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static 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.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelConstantCanonicalizationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    int sdkInt = parameters.isCfRuntime() ? 0 : parameters.getApiLevel().getLevel();
+    List<String> outputLines = new ArrayList<>();
+    if (sdkInt < 22) {
+      outputLines.add("No cigar!");
+    } else if (sdkInt == 22) {
+      outputLines.add("apiLevel22");
+    } else {
+      outputLines.add("apiLevel23");
+    }
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class, Version.class)
+        .addLibraryClasses(ApiLevel22.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-assumevalues class " + Version.class.getTypeName() + " {",
+            "  public static int getSdkInt(int) return " + sdkInt + "..42;",
+            "}")
+        .apply(setMockApiLevelForClass(ApiLevel22.class, AndroidApiLevel.L_MR1))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              List<InstructionSubject> filteredInstructions =
+                  inspector
+                      .clazz(A.class)
+                      .uniqueMethodWithOriginalName("m")
+                      .streamInstructions()
+                      .filter(i -> i.isInstanceGet() | i.isIf())
+                      .collect(Collectors.toList());
+              // The instance get can only be moved before the if, when the type of the field is
+              // safe to reference on all supported API levels.
+              if (sdkInt < 22) {
+                assertTrue(filteredInstructions.get(0).isIf());
+                assertTrue(filteredInstructions.get(1).isInstanceGet());
+              } else {
+                assertTrue(filteredInstructions.get(0).isInstanceGet());
+                assertTrue(filteredInstructions.get(1).isIf());
+              }
+            })
+        .addRunClasspathClasses(ApiLevel22.class)
+        .run(parameters.getRuntime(), Main.class, Integer.toString(sdkInt))
+        .assertSuccessWithOutputLines(outputLines);
+  }
+
+  public static class ApiLevel22 {
+
+    public void apiLevel22() {
+      System.out.println("apiLevel22");
+    }
+
+    public void apiLevel23() {
+      System.out.println("apiLevel23");
+    }
+  }
+
+  public static class A {
+
+    private final ApiLevel22 f;
+
+    A(int sdk) {
+      f = Version.getSdkInt(sdk) >= 22 ? new ApiLevel22() : null;
+    }
+
+    public void m(int sdk) {
+      if (sdk >= 23) {
+        if (f != null) {
+          f.apiLevel23();
+        }
+      } else if (sdk == 22) {
+        if (f != null) {
+          f.apiLevel22();
+        }
+      } else {
+        System.out.println("No cigar!");
+      }
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      int sdk = Integer.parseInt(args[0]);
+      A a = new A(sdk);
+      a.m(sdk);
+    }
+  }
+
+  public static class Version {
+
+    // -assumevalues ...
+    public static int getSdkInt(int sdk) {
+      return sdk;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
index 10b38c6..c1ed5f3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
@@ -168,13 +168,8 @@
                 "foo",
                 Collections.emptyList(),
                 null));
-    if (isR8 && parameters.isCfRuntime()) {
-      verifyHelper.isOutlinedFromUntil(
-          Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
-    } else {
-      verifyHelper.isOutlinedFromUntilAlsoForCf(
-          Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
-    }
+    verifyHelper.isOutlinedFromUntil(
+        Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
index d01a450..1ee2689 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
@@ -150,13 +150,8 @@
                 "foo",
                 Collections.emptyList(),
                 null));
-    if (isR8 && parameters.isCfRuntime()) {
-      verifyHelper.isOutlinedFromUntil(
-          Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
-    } else {
-      verifyHelper.isOutlinedFromUntilAlsoForCf(
-          Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
-    }
+    verifyHelper.isOutlinedFromUntil(
+        Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
   }
 
   // Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
index 4c6763e..ae77d7b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -17,6 +18,8 @@
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -38,12 +41,17 @@
   }
 
   private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    setupTestBuilderWithoutProgram(
+        testBuilder.addProgramClasses(Main.class).addAndroidBuildVersion(getApiLevelForRuntime()));
+  }
+
+  private void setupTestBuilderWithoutProgram(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
     testBuilder
+        .markAndroidBuildVersionAsActive(getApiLevelForRuntime())
         .addLibraryClasses(LibraryClass.class, LibraryProvider.class)
         .addDefaultRuntimeLibrary(parameters)
-        .addProgramClasses(Main.class)
         .setMinApi(parameters)
-        .addAndroidBuildVersion(getApiLevelForRuntime())
         .apply(setMockApiLevelForClass(LibraryProvider.class, AndroidApiLevel.B))
         .apply(
             setMockApiLevelForMethod(
@@ -78,6 +86,52 @@
   }
 
   @Test
+  public void testD8Cf() throws Exception {
+    parameters.assumeCfRuntime();
+    testForD8(parameters.getBackend())
+        .setMinApi(parameters)
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(
+            inspector ->
+                // Compiling to CF should never result in stubs and outlining for API modeling.
+                verifyThat(inspector, parameters, LibraryClass.class)
+                    .hasNotCheckCastOutlinedFrom(getMainMethod()))
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8CfAndDexNoDesugaring() throws Exception {
+    parameters.assumeDexRuntime();
+    D8TestCompileResult compileResult =
+        testForD8(Backend.CF)
+            .setMinApi(parameters)
+            .setMode(CompilationMode.DEBUG)
+            .apply(this::setupTestBuilder)
+            .compile();
+    Path out = compileResult.writeToZip();
+    testForD8()
+        .disableDesugaring()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilderWithoutProgram)
+        .addProgramFiles(out)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
   public void testD8Debug() throws Exception {
     parameters.assumeDexRuntime();
     testForD8()
@@ -127,7 +181,11 @@
 
   private void inspect(CodeInspector inspector) throws Exception {
     verifyThat(inspector, parameters, LibraryClass.class)
-        .hasCheckCastOutlinedFromUntil(Main.class.getMethod("main", String[].class), classApiLevel);
+        .hasCheckCastOutlinedFromUntil(getMainMethod(), classApiLevel);
+  }
+
+  private static Method getMainMethod() throws NoSuchMethodException {
+    return Main.class.getMethod("main", String[].class);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 33fe85c..c546154 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -138,27 +138,33 @@
       TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
-          options.apiModelingOptions().enableLibraryApiModeling = true;
-          options.apiModelingOptions().enableStubbingOfClasses = true;
-          // Our tests rely on us amending the library path with additional classes that are not
-          // in the library.
-          options.testing.globalSyntheticCreatedCallback = null;
+          if (options.isGeneratingDex()) {
+            options.apiModelingOptions().enableLibraryApiModeling = true;
+            options.apiModelingOptions().enableStubbingOfClasses = true;
+            // Our tests rely on us amending the library path with additional classes that are not
+            // in the library.
+            options.testing.globalSyntheticCreatedCallback = null;
+          }
         });
   }
 
   public static void enableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
-          options.apiModelingOptions().enableLibraryApiModeling = true;
-          options.apiModelingOptions().enableStubbingOfClasses = true;
+          if (options.isGeneratingDex()) {
+            options.apiModelingOptions().enableLibraryApiModeling = true;
+            options.apiModelingOptions().enableStubbingOfClasses = true;
+          }
         });
   }
 
   public static void enableOutliningOfMethods(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
-          options.apiModelingOptions().enableLibraryApiModeling = true;
-          options.apiModelingOptions().enableOutliningOfMethods = true;
+          if (options.isGeneratingDex()) {
+            options.apiModelingOptions().enableLibraryApiModeling = true;
+            options.apiModelingOptions().enableOutliningOfMethods = true;
+          }
         });
   }
 
@@ -496,14 +502,6 @@
       }
     }
 
-    void isOutlinedFromUntilAlsoForCf(Executable method, AndroidApiLevel apiLevel) {
-      if (parameters.getApiLevel().isLessThan(apiLevel)) {
-        isOutlinedFrom(method);
-      } else {
-        isNotOutlinedFrom(method);
-      }
-    }
-
     void isOutlinedFrom(Executable method) {
       // Check that the call is in a synthetic class.
       List<FoundMethodSubject> outlinedMethod =
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java
index 164755b..f0de7ae 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClInitMergeSuperTypeApiLevelTest.java
@@ -6,10 +6,12 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -39,11 +41,14 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  private boolean canUseExecutable() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+  }
+
   private TypeReference getMergeReferenceForApiLevel() {
-    boolean canUseExecutable =
-        parameters.isDexRuntime()
-            && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
-    return Reference.typeFromTypeName(typeName(canUseExecutable ? Executable.class : Object.class));
+    return Reference.typeFromTypeName(
+        typeName(canUseExecutable() ? Executable.class : Object.class));
   }
 
   @Test
@@ -59,17 +64,34 @@
                 inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .compile()
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(A.class);
               assertThat(clazz, isPresent());
-              TypeReference mergeTypeRef = getMergeReferenceForApiLevel();
-              MethodSubject init = clazz.init(mergeTypeRef.getTypeName(), "int");
-              assertThat(init, isPresent());
+
               assertTrue(
                   clazz.allFields().stream()
-                      .anyMatch(f -> mergeTypeRef.equals(f.getFinalReference().getFieldType())));
+                      .anyMatch(
+                          f ->
+                              f.getFinalReference()
+                                  .getFieldType()
+                                  .equals(getMergeReferenceForApiLevel())));
+
+              assertEquals(
+                  canUseExecutable() ? 1 : 2,
+                  clazz.allMethods(MethodSubject::isInstanceInitializer).size());
+              if (canUseExecutable()) {
+                MethodSubject constructorInit = clazz.init(Executable.class.getTypeName(), "int");
+                assertThat(constructorInit, isPresent());
+              } else {
+                MethodSubject constructorInit = clazz.init(Constructor.class.getTypeName());
+                assertThat(constructorInit, isPresent());
+
+                MethodSubject methodInit = clazz.init(Method.class.getTypeName());
+                assertThat(methodInit, isPresent());
+              }
             })
         .run(parameters.getRuntime(), Main.class)
         // The test succeeds for some unknown reason.
@@ -92,6 +114,7 @@
     }
   }
 
+  @NoVerticalClassMerging
   public abstract static class Factory {
 
     abstract Object newInstance() throws Exception;
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.java
new file mode 100644
index 0000000..dac6de6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.classmerging.horizontal.EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest.Main.encode;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NoFieldTypeStrengthening;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentInstanceInitializerMergingWithApiUnsafeParameterTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, Parent.class, A.class, B.class, MyLibraryClass.class)
+        .addLibraryClasses(LibraryClassBase.class, LibraryClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addKeepClassAndMembersRules(Main.class, Parent.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableNoFieldTypeStrengtheningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              // Verify that the two constructors A.<init> and B.<init> have not been merged.
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertEquals(
+                  2, aClassSubject.allMethods(MethodSubject::isInstanceInitializer).size());
+            })
+        .addRunClasspathClasses(LibraryClassBase.class, LibraryClass.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("LibraryClass", "MyLibraryClass");
+  }
+
+  static class LibraryClassBase {}
+
+  static class LibraryClass extends LibraryClassBase {
+
+    @Override
+    public String toString() {
+      return "LibraryClass";
+    }
+  }
+
+  // @Keep
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new LibraryClass()));
+      System.out.println(new B(new MyLibraryClass()));
+    }
+
+    public static String encode(Object o) {
+      return o.toString();
+    }
+  }
+
+  // @Keep
+  static class Parent {
+
+    Parent(LibraryClass lib) {}
+  }
+
+  static class A extends Parent {
+
+    @NoFieldTypeStrengthening LibraryClassBase f;
+
+    A(LibraryClass c) {
+      super(c);
+      f = c;
+    }
+
+    @Override
+    public String toString() {
+      // Use `f` to ensure it is not removed.
+      return encode(f);
+    }
+  }
+
+  static class B extends Parent {
+
+    @NoFieldTypeStrengthening LibraryClassBase f;
+
+    B(MyLibraryClass d) {
+      super(d);
+      f = d;
+    }
+
+    @Override
+    public String toString() {
+      // Use `f` to ensure it is not removed.
+      return encode(f);
+    }
+  }
+
+  static class MyLibraryClass extends LibraryClass {
+
+    @Override
+    public String toString() {
+      return "MyLibraryClass";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
index 11a96b7..250a8e9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
@@ -5,18 +5,13 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThrows;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -30,7 +25,7 @@
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters()
-        .withDexRuntimes()
+        .withDefaultDexRuntime()
         .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
         .build();
   }
@@ -39,42 +34,23 @@
   //  Ensure the main-dex-rules variant of this test (PreventMergeMainDexTracingTest) is sufficient.
   @Test
   public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepClassAndMembersRules(Main.class)
-        .addMainDexListClasses(A.class, Main.class)
-        .addOptionsModification(options -> options.minimalMainDex = true)
-        .enableNeverClassInliningAnnotations()
-        .setMinApi(parameters)
-        .allowDiagnosticMessages()
-        .compileWithExpectedDiagnostics(
-            diagnostics ->
-                diagnostics
-                    .assertOnlyWarnings()
-                    .assertWarningsMatch(
-                        diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)))
-        .apply(this::checkCompileResult)
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("main dex");
-  }
-
-  private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
-    Path out = temp.newFolder().toPath();
-    compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
-    Path classes = out.resolve("classes.dex");
-    Path classes2 = out.resolve("classes2.dex");
-    inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
-    inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
-  }
-
-  private void inspectMainDex(CodeInspector inspector) {
-    assertThat(inspector.clazz(A.class), isPresent());
-    assertThat(inspector.clazz(B.class), not(isPresent()));
-  }
-
-  private void inspectSecondaryDex(CodeInspector inspector) {
-    assertThat(inspector.clazz(A.class), not(isPresent()));
-    assertThat(inspector.clazz(B.class), isPresent());
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepClassAndMembersRules(Main.class)
+                .addMainDexListClasses(A.class, Main.class)
+                .addOptionsModification(options -> options.minimalMainDex = true)
+                .enableNeverClassInliningAnnotations()
+                .setMinApi(parameters)
+                .allowDiagnosticMessages()
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics
+                            .assertOnlyErrors()
+                            .assertErrorsMatch(
+                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class))));
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index c50b238..5d349fc 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -20,11 +20,16 @@
 import com.android.tools.r8.compilerapi.globalsyntheticsgenerator.GlobalSyntheticsGeneratorTest;
 import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
 import com.android.tools.r8.compilerapi.inputmap.InputMapTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexClassesTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexListTest;
+import com.android.tools.r8.compilerapi.maindex.MainDexRulesTest;
 import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
 import com.android.tools.r8.compilerapi.mockdata.MockClass;
 import com.android.tools.r8.compilerapi.mockdata.MockClassWithAssertion;
 import com.android.tools.r8.compilerapi.mockdata.PostStartupMockClass;
 import com.android.tools.r8.compilerapi.partitionmap.PartitionMapCommandTest;
+import com.android.tools.r8.compilerapi.sampleapi.D8ApiUsageSampleTest;
+import com.android.tools.r8.compilerapi.sampleapi.R8ApiUsageSampleTest;
 import com.android.tools.r8.compilerapi.sourcefile.CustomSourceFileTest;
 import com.android.tools.r8.compilerapi.startupprofile.StartupProfileApiTest;
 import com.android.tools.r8.compilerapi.syntheticscontexts.SyntheticContextsConsumerTest;
@@ -47,6 +52,8 @@
   private static final List<Class<? extends CompilerApiTest>> CLASSES_FOR_BINARY_COMPATIBILITY =
       ImmutableList.of(
           ApiTestingSetUpTest.ApiTest.class,
+          D8ApiUsageSampleTest.ApiTest.class,
+          R8ApiUsageSampleTest.ApiTest.class,
           CustomMapIdTest.ApiTest.class,
           CustomSourceFileTest.ApiTest.class,
           AssertionConfigurationTest.ApiTest.class,
@@ -66,7 +73,10 @@
           PartitionMapCommandTest.ApiTest.class,
           CancelCompilationCheckerTest.ApiTest.class,
           GlobalSyntheticsGeneratorTest.ApiTest.class,
-          InputMapTest.ApiTest.class);
+          InputMapTest.ApiTest.class,
+          MainDexListTest.ApiTest.class,
+          MainDexClassesTest.ApiTest.class,
+          MainDexRulesTest.ApiTest.class);
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
       ImmutableList.of();
diff --git a/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
index 6cab4d7..c3e49a5 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
@@ -3,13 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compilerapi.globalsyntheticsgenerator;
 
-import com.android.tools.r8.DexIndexedConsumer;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
 import com.android.tools.r8.GlobalSyntheticsGenerator;
 import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
-import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.compilerapi.CompilerApiTest;
 import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.references.ClassReference;
 import org.junit.Test;
 
 public class GlobalSyntheticsGeneratorTest extends CompilerApiTestRunner {
@@ -27,8 +33,23 @@
   public void testGlobalSynthetics() throws Exception {
     new ApiTest(ApiTest.PARAMETERS)
         .run(
-            new DexIndexedConsumer.ArchiveConsumer(
-                temp.newFolder().toPath().resolve("output.zip")));
+            new GlobalSyntheticsConsumer() {
+              boolean hasOutput = false;
+
+              @Override
+              public void accept(
+                  ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+                assertNull(context);
+                assertFalse(hasOutput);
+                assertTrue(data.getLength() > 0);
+                hasOutput = true;
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                assertTrue(hasOutput);
+              }
+            });
   }
 
   public static class ApiTest extends CompilerApiTest {
@@ -37,18 +58,25 @@
       super(parameters);
     }
 
-    public void run(ProgramConsumer programConsumer) throws Exception {
+    public void run(GlobalSyntheticsConsumer consumer) throws Exception {
       GlobalSyntheticsGenerator.run(
           GlobalSyntheticsGeneratorCommand.builder()
               .addLibraryFiles(getAndroidJar())
               .setMinApiLevel(33)
-              .setProgramConsumer(programConsumer)
+              .setGlobalSyntheticsConsumer(consumer)
               .build());
     }
 
     @Test
     public void testGlobalSynthetics() throws Exception {
-      run(DexIndexedConsumer.emptyConsumer());
+      run(
+          new GlobalSyntheticsConsumer() {
+            @Override
+            public void accept(
+                ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+              // Ignoring output in API test.
+            }
+          });
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java
new file mode 100644
index 0000000..f204285
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexClassesTest.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2024, 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.compilerapi.maindex;
+
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import java.nio.file.Path;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class MainDexClassesTest extends CompilerApiTestRunner {
+
+  public MainDexClassesTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  static class InputClass {}
+
+  interface Runner {
+    void run(ApiTest test, String[] mainDexClasses, DiagnosticsHandler handler) throws Exception;
+  }
+
+  TestDiagnosticMessagesImpl handler;
+
+  private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    String[] mainDexClasses = new String[] {InputClass.class.getName()};
+    handler = new TestDiagnosticMessagesImpl();
+    runner.run(test, mainDexClasses, handler);
+    return handler;
+  }
+
+  private Path getDexInput() throws Exception {
+    return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+  }
+
+  private static Path getCfInput() {
+    return ToolHelper.getClassFileForTestClass(InputClass.class);
+  }
+
+  @Test
+  public void testD8DexInputs() throws Exception {
+    runTest((test, mainDexClasses, handler) -> test.runD8(getDexInput(), mainDexClasses, handler))
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testD8CfInputs() throws Exception {
+    try {
+      runTest((test, mainDexClasses, handler) -> test.runD8(getCfInput(), mainDexClasses, handler));
+    } catch (CompilationFailedException e) {
+      handler
+          .assertOnlyErrors()
+          .assertAllErrorsMatch(
+              DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+      return;
+    }
+    fail("Expected compilation failure");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    try {
+      runTest((test, mainDexClasses, handler) -> test.runR8(getCfInput(), mainDexClasses, handler));
+    } catch (CompilationFailedException e) {
+      handler
+          .assertOnlyErrors()
+          .assertAllErrorsMatch(
+              DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+      return;
+    }
+    fail("Expected compilation failure");
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runD8(Path programInput, String[] mainDexClassesInput, DiagnosticsHandler handler)
+        throws CompilationFailedException {
+      D8Command.Builder builder =
+          handler == null ? D8Command.builder() : D8Command.builder(handler);
+      builder
+          .addLibraryFiles(getJava8RuntimeJar())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      if (programInput != null) {
+        builder.addProgramFiles(programInput);
+      }
+      if (mainDexClassesInput != null) {
+        builder.addMainDexClasses(mainDexClassesInput);
+        builder.addMainDexClasses(Arrays.asList(mainDexClassesInput));
+      }
+      D8.run(builder.build());
+    }
+
+    public void runR8(Path programInput, String[] mainDexClassesInput, DiagnosticsHandler handler)
+        throws CompilationFailedException {
+      R8Command.Builder builder =
+          handler == null ? R8Command.builder() : R8Command.builder(handler);
+      builder
+          .addLibraryFiles(getJava8RuntimeJar())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      if (programInput != null) {
+        builder.addProgramFiles(programInput);
+      }
+      builder.addMainDexClasses(mainDexClassesInput);
+      builder.addMainDexClasses(Arrays.asList(mainDexClassesInput));
+      R8.run(builder.build());
+    }
+
+    @Test
+    public void testD8() throws CompilationFailedException {
+      runD8(null, new String[0], null);
+    }
+
+    @Test
+    public void testR8() throws CompilationFailedException {
+      runR8(null, new String[0], null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java
new file mode 100644
index 0000000..47430fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexListTest.java
@@ -0,0 +1,148 @@
+// Copyright (c) 2024, 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.compilerapi.maindex;
+
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import com.android.tools.r8.utils.FileUtils;
+import java.nio.file.Path;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MainDexListTest extends CompilerApiTestRunner {
+
+  public MainDexListTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  static class InputClass {}
+
+  interface Runner {
+    void run(ApiTest test, Path mainDexList, DiagnosticsHandler handler) throws Exception;
+  }
+
+  TestDiagnosticMessagesImpl handler;
+
+  private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    Path mainDexListInput =
+        FileUtils.writeTextFile(
+            temp.newFile().toPath(), InputClass.class.getName().replace('.', '/') + ".class");
+    handler = new TestDiagnosticMessagesImpl();
+    runner.run(test, mainDexListInput, handler);
+    return handler;
+  }
+
+  private Path getDexInput() throws Exception {
+    return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+  }
+
+  private static Path getCfInput() {
+    return ToolHelper.getClassFileForTestClass(InputClass.class);
+  }
+
+  @Test
+  public void testD8DexInputs() throws Exception {
+    runTest((test, mainDexList, handler) -> test.runD8(getDexInput(), mainDexList, handler))
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testD8CfInputs() throws Exception {
+    try {
+      runTest((test, mainDexList, handler) -> test.runD8(getCfInput(), mainDexList, handler));
+    } catch (CompilationFailedException e) {
+      handler
+          .assertOnlyErrors()
+          .assertAllErrorsMatch(
+              DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+      return;
+    }
+    fail("Expected compilation failure");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    try {
+      runTest((test, mainDexList, handler) -> test.runR8(getCfInput(), mainDexList, handler));
+    } catch (CompilationFailedException e) {
+      handler
+          .assertOnlyErrors()
+          .assertAllErrorsMatch(
+              DiagnosticsMatcher.diagnosticType(UnsupportedMainDexListUsageDiagnostic.class));
+      return;
+    }
+    fail("Expected compilation failure");
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runD8(Path programInput, Path mainDexListInput, DiagnosticsHandler handler)
+        throws CompilationFailedException {
+      D8Command.Builder builder =
+          handler == null ? D8Command.builder() : D8Command.builder(handler);
+      builder
+          .addLibraryFiles(getJava8RuntimeJar())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      if (programInput != null) {
+        builder.addProgramFiles(programInput);
+      }
+      if (mainDexListInput != null) {
+        builder.addMainDexListFiles(mainDexListInput);
+        builder.addMainDexListFiles(Collections.singletonList(mainDexListInput));
+      }
+      D8.run(builder.build());
+    }
+
+    public void runR8(Path programInput, Path mainDexListInput, DiagnosticsHandler handler)
+        throws CompilationFailedException {
+      R8Command.Builder builder =
+          handler == null ? R8Command.builder() : R8Command.builder(handler);
+      builder
+          .addLibraryFiles(getJava8RuntimeJar())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      if (programInput != null) {
+        builder.addProgramFiles(programInput);
+      }
+      if (mainDexListInput != null) {
+        builder.addMainDexListFiles(mainDexListInput);
+        builder.addMainDexListFiles(Collections.singletonList(mainDexListInput));
+      }
+      R8.run(builder.build());
+    }
+
+    @Test
+    public void testD8() throws CompilationFailedException {
+      runD8(null, null, null);
+    }
+
+    @Test
+    public void testR8() throws CompilationFailedException {
+      runR8(null, null, null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java
new file mode 100644
index 0000000..5701058
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/maindex/MainDexRulesTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2024, 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.compilerapi.maindex;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class MainDexRulesTest extends CompilerApiTestRunner {
+
+  public MainDexRulesTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  static class InputClass {}
+
+  interface Runner {
+    void run(ApiTest test, Path mainDexList, DiagnosticsHandler handler) throws Exception;
+  }
+
+  private TestDiagnosticMessagesImpl runTest(Runner runner) throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    Path mainDexRulesFile = FileUtils.writeTextFile(temp.newFile().toPath(), "# empty file");
+    TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
+    runner.run(test, mainDexRulesFile, handler);
+    return handler;
+  }
+
+  private Path getDexInput() throws Exception {
+    return testForD8().addProgramClasses(InputClass.class).compile().writeToZip();
+  }
+
+  private static Path getCfInput() {
+    return ToolHelper.getClassFileForTestClass(InputClass.class);
+  }
+
+  @Test
+  public void testD8DexInputs() throws Exception {
+    runTest((test, mainDexList, handler) -> test.runD8(getDexInput(), mainDexList, handler))
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testD8CfInputs() throws Exception {
+    runTest((test, mainDexList, handler) -> test.runD8(getCfInput(), mainDexList, handler))
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTest((test, mainDexList, handler) -> test.runR8(getCfInput(), mainDexList, handler))
+        .assertNoMessages();
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runD8(Path programInput, Path mainDexRulesFile, DiagnosticsHandler handler)
+        throws Exception {
+      D8Command.Builder builder =
+          handler == null ? D8Command.builder() : D8Command.builder(handler);
+      builder
+          .addLibraryFiles(getJava8RuntimeJar())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      if (programInput != null) {
+        builder.addProgramFiles(programInput);
+      }
+      if (mainDexRulesFile != null) {
+        List<String> rules = Files.readAllLines(mainDexRulesFile);
+        builder.addMainDexRules(rules, new PathOrigin(mainDexRulesFile));
+        builder.addMainDexRulesFiles(mainDexRulesFile);
+        builder.addMainDexRulesFiles(Collections.singletonList(mainDexRulesFile));
+      }
+      D8.run(builder.build());
+    }
+
+    public void runR8(Path programInput, Path mainDexRulesFile, DiagnosticsHandler handler)
+        throws CompilationFailedException {
+      R8Command.Builder builder =
+          handler == null ? R8Command.builder() : R8Command.builder(handler);
+      builder
+          .addLibraryFiles(getJava8RuntimeJar())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      if (programInput != null) {
+        builder.addProgramFiles(programInput);
+      }
+      if (mainDexRulesFile != null) {
+        List<String> rules;
+        try {
+          rules = Files.readAllLines(mainDexRulesFile);
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+        builder.addMainDexRules(rules, new PathOrigin(mainDexRulesFile));
+        builder.addMainDexRulesFiles(mainDexRulesFile);
+        builder.addMainDexRulesFiles(Collections.singletonList(mainDexRulesFile));
+      }
+      R8.run(builder.build());
+    }
+
+    @Test
+    public void testD8() throws Exception {
+      runD8(null, null, null);
+    }
+
+    @Test
+    public void testR8() throws Exception {
+      runR8(null, null, null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java
new file mode 100644
index 0000000..7a71205
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/D8ApiUsageSampleTest.java
@@ -0,0 +1,541 @@
+// Copyright (c) 2024, 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.compilerapi.sampleapi;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+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.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * This API test is a copy over from the old API sample test set up.
+ *
+ * <p>NOTE: Don't use this test as the basis for new API tests. Instead, use one of the simpler and
+ * more feature directed tests found in the sibling packages.
+ */
+public class D8ApiUsageSampleTest extends CompilerApiTestRunner {
+
+  public D8ApiUsageSampleTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void test() throws IOException {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    test.run(temp);
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    private static final Origin origin =
+        new Origin(Origin.root()) {
+          @Override
+          public String part() {
+            return "D8ApiUsageSample";
+          }
+        };
+
+    private static final DiagnosticsHandler handler = new DiagnosticsHandler() {};
+
+    @Test
+    public void test() throws IOException {
+      run(temp);
+    }
+
+    public void run(TemporaryFolder temp) throws IOException {
+      runFromArgs(
+          new String[] {
+            "--output",
+            temp.newFolder().getAbsolutePath(),
+            "--min-api",
+            "19",
+            "--lib",
+            getAndroidJar().toString(),
+            "--classpath",
+            getAndroidJar().toString(),
+            getPathForClass(getMockClass()).toString()
+          });
+    }
+
+    public void runFromArgs(String[] args) {
+      // Parse arguments with the commandline parser to make use of its API.
+      D8Command.Builder cmd = D8Command.parse(args, origin);
+      CompilationMode mode = cmd.getMode();
+      Path temp = cmd.getOutputPath();
+      int minApiLevel = cmd.getMinApiLevel();
+      // The Builder API does not provide access to the concrete paths
+      // (everything is put into providers) so manually parse them here.
+      List<Path> libraries = new ArrayList<>(1);
+      List<Path> classpath = new ArrayList<>(args.length);
+      List<Path> inputs = new ArrayList<>(args.length);
+      for (int i = 0; i < args.length; i++) {
+        if (args[i].equals("--lib")) {
+          libraries.add(Paths.get(args[++i]));
+        } else if (args[i].equals("--classpath")) {
+          classpath.add(Paths.get(args[++i]));
+        } else if (isArchive(args[i]) || isClassFile(args[i])) {
+          inputs.add(Paths.get(args[i]));
+        }
+      }
+      if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+        throw new RuntimeException("Must supply a temp/output directory");
+      }
+      if (inputs.isEmpty()) {
+        throw new RuntimeException("Must supply program inputs");
+      }
+      if (libraries.isEmpty()) {
+        throw new RuntimeException("Must supply library inputs");
+      }
+      useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, classpath, inputs);
+      useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, classpath, inputs);
+      useProgramData(minApiLevel, libraries, classpath, inputs);
+      useProgramResourceProvider(minApiLevel, libraries, classpath, inputs);
+      useLibraryAndClasspathProvider(minApiLevel, libraries, classpath, inputs);
+      useAssertionConfig(minApiLevel, libraries, classpath, inputs);
+      useVArgVariants(minApiLevel, libraries, classpath, inputs);
+      incrementalCompileAndMerge(minApiLevel, libraries, classpath, inputs);
+    }
+
+    // Check API support for compiling Java class-files from the file system.
+    private static void useProgramFileList(
+        CompilationMode mode,
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      try {
+        D8.run(
+            D8Command.builder(handler)
+                .setMode(mode)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addClasspathFiles(classpath)
+                .addProgramFiles(inputs)
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    // Check API support for compiling Java class-files from byte content.
+    private static void useProgramData(
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      try {
+        D8Command.Builder builder =
+            D8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addClasspathFiles(classpath);
+        for (ClassFileContent classfile : readClassFiles(inputs)) {
+          builder.addClassProgramData(classfile.data, classfile.origin);
+        }
+        for (Path input : inputs) {
+          if (isDexFile(input)) {
+            builder.addDexProgramData(Files.readAllBytes(input), new PathOrigin(input));
+          }
+        }
+        D8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      } catch (IOException e) {
+        throw new RuntimeException("Unexpected IO exception", e);
+      }
+    }
+
+    // Check API support for compiling Java class-files from a program provider abstraction.
+    private static void useProgramResourceProvider(
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      try {
+        D8Command.Builder builder =
+            D8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addClasspathFiles(classpath);
+        for (Path input : inputs) {
+          if (isArchive(input)) {
+            builder.addProgramResourceProvider(
+                ArchiveProgramResourceProvider.fromArchive(
+                    input, ArchiveProgramResourceProvider::includeClassFileEntries));
+          } else if (isClassFile(input)) {
+            builder.addProgramResourceProvider(
+                new ProgramResourceProvider() {
+                  @Override
+                  public Collection<ProgramResource> getProgramResources()
+                      throws ResourceException {
+                    return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+                  }
+                });
+          } else if (isDexFile(input)) {
+            builder.addProgramResourceProvider(
+                new ProgramResourceProvider() {
+                  @Override
+                  public Collection<ProgramResource> getProgramResources()
+                      throws ResourceException {
+                    return Collections.singleton(ProgramResource.fromFile(Kind.DEX, input));
+                  }
+                });
+          }
+        }
+        D8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    private static void useLibraryAndClasspathProvider(
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      try {
+        D8Command.Builder builder =
+            D8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addProgramFiles(inputs);
+        for (Path library : libraries) {
+          builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+        }
+        for (Path path : classpath) {
+          builder.addClasspathResourceProvider(new ArchiveClassFileProvider(path));
+        }
+        D8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      } catch (IOException e) {
+        throw new RuntimeException("Unexpected IO exception", e);
+      }
+    }
+
+    private static void useAssertionConfig(
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      String pkg = "com.android.tools.r8.compilerapi.sampleapi";
+      try {
+        D8.run(
+            D8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addClasspathFiles(classpath)
+                .addProgramFiles(inputs)
+                .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
+                .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
+                .addAssertionsConfiguration(
+                    b -> b.setScopePackage(pkg).setCompileTimeEnable().build())
+                .addAssertionsConfiguration(b -> b.setScopePackage(pkg).setPassthrough().build())
+                .addAssertionsConfiguration(
+                    b -> b.setScopePackage(pkg).setCompileTimeDisable().build())
+                .addAssertionsConfiguration(
+                    b ->
+                        b.setScopeClass(pkg + ".D8ApiUsageSampleTest")
+                            .setCompileTimeEnable()
+                            .build())
+                .addAssertionsConfiguration(
+                    b -> b.setScopeClass(pkg + ".D8ApiUsageSampleTest").setPassthrough().build())
+                .addAssertionsConfiguration(
+                    b ->
+                        b.setScopeClass(pkg + ".D8ApiUsageSampleTest")
+                            .setCompileTimeDisable()
+                            .build())
+                .addAssertionsConfiguration(
+                    AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
+                .addAssertionsConfiguration(
+                    AssertionsConfiguration.Builder::passthroughAllAssertions)
+                .addAssertionsConfiguration(
+                    AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    // Check API support for all the varg variants.
+    private static void useVArgVariants(
+        int minApiLevel, List<Path> libraries, List<Path> classpath, List<Path> inputs) {
+      try {
+        D8Command.Builder builder =
+            D8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries.get(0))
+                .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+                .addProgramFiles(inputs.get(0))
+                .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new));
+        if (!classpath.isEmpty()) {
+          builder
+              .addClasspathFiles(classpath.get(0))
+              .addClasspathFiles(classpath.stream().skip(1).toArray(Path[]::new));
+        }
+        D8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    private static void incrementalCompileAndMerge(
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      // Compile and merge via index intermediates.
+      mergeIntermediates(
+          minApiLevel, compileToIndexedIntermediates(minApiLevel, libraries, classpath, inputs));
+      // Compile and merge via per-classfile intermediates.
+      mergeIntermediates(
+          minApiLevel,
+          compileToPerClassFileIntermediates(minApiLevel, libraries, classpath, inputs));
+    }
+
+    private static Collection<byte[]> compileToIndexedIntermediates(
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      IndexIntermediatesConsumer consumer = new IndexIntermediatesConsumer();
+      try {
+        D8.run(
+            D8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setIntermediate(true)
+                .setProgramConsumer(consumer)
+                .addClasspathFiles(classpath)
+                .addLibraryFiles(libraries)
+                .addProgramFiles(inputs)
+                .setDisableDesugaring(false)
+                .setDesugarGraphConsumer(new MyDesugarGraphConsumer())
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+      return consumer.bytes;
+    }
+
+    private static Collection<byte[]> compileToPerClassFileIntermediates(
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> classpath,
+        Collection<Path> inputs) {
+      PerClassIntermediatesConsumer consumer = new PerClassIntermediatesConsumer();
+      try {
+        D8.run(
+            D8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(consumer)
+                .addLibraryFiles(libraries)
+                .addClasspathFiles(classpath)
+                .addProgramFiles(inputs)
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+      return consumer.bytes;
+    }
+
+    private static void mergeIntermediates(int minApiLevel, Collection<byte[]> intermediates) {
+      D8Command.Builder builder =
+          D8Command.builder(handler)
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(new EnsureOutputConsumer())
+              .setDisableDesugaring(true);
+      for (byte[] intermediate : intermediates) {
+        builder.addDexProgramData(intermediate, Origin.unknown());
+      }
+      try {
+        D8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected merging error", e);
+      }
+    }
+
+    // Helpers for tests.
+    // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+    // rely on it.
+
+    private static List<ClassFileContent> readClassFiles(Collection<Path> files)
+        throws IOException {
+      List<ClassFileContent> classfiles = new ArrayList<>();
+      for (Path file : files) {
+        if (isArchive(file)) {
+          Origin zipOrigin = new PathOrigin(file);
+          ZipInputStream zip =
+              new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
+          ZipEntry entry;
+          while (null != (entry = zip.getNextEntry())) {
+            String name = entry.getName();
+            if (isClassFile(name)) {
+              Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
+              classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+            }
+          }
+        } else if (isClassFile(file)) {
+          classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+        }
+      }
+      return classfiles;
+    }
+
+    private static byte[] readBytes(InputStream stream) throws IOException {
+      try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+        byte[] buffer = new byte[0xffff];
+        for (int length; (length = stream.read(buffer)) != -1; ) {
+          bytes.write(buffer, 0, length);
+        }
+        return bytes.toByteArray();
+      }
+    }
+
+    private static String toLowerCase(String str) {
+      return str.toLowerCase(Locale.ROOT);
+    }
+
+    private static boolean isClassFile(Path file) {
+      return isClassFile(file.toString());
+    }
+
+    private static boolean isClassFile(String file) {
+      file = toLowerCase(file);
+      return file.endsWith(".class");
+    }
+
+    private static boolean isDexFile(Path file) {
+      return isDexFile(file.toString());
+    }
+
+    private static boolean isDexFile(String file) {
+      file = toLowerCase(file);
+      return file.endsWith(".dex");
+    }
+
+    private static boolean isArchive(Path file) {
+      return isArchive(file.toString());
+    }
+
+    private static boolean isArchive(String file) {
+      file = toLowerCase(file);
+      return file.endsWith(".zip") || file.endsWith(".jar");
+    }
+
+    private static class ClassFileContent {
+      final Origin origin;
+      final byte[] data;
+
+      public ClassFileContent(Origin origin, byte[] data) {
+        this.origin = origin;
+        this.data = data;
+      }
+    }
+
+    private static class IndexIntermediatesConsumer implements DexIndexedConsumer {
+
+      List<byte[]> bytes = new ArrayList<>();
+
+      @Override
+      public synchronized void accept(
+          int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        bytes.add(data);
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {}
+    }
+
+    private static class PerClassIntermediatesConsumer implements DexFilePerClassFileConsumer {
+
+      List<byte[]> bytes = new ArrayList<>();
+
+      @Override
+      public synchronized void accept(
+          String primaryClassDescriptor,
+          byte[] data,
+          Set<String> descriptors,
+          DiagnosticsHandler handler) {
+        bytes.add(data);
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {}
+    }
+
+    private static class EnsureOutputConsumer implements DexIndexedConsumer {
+      boolean hasOutput = false;
+
+      @Override
+      public synchronized void accept(
+          int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        hasOutput = true;
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        if (!hasOutput) {
+          handler.error(new StringDiagnostic("Expected to produce output but had none"));
+        }
+      }
+    }
+
+    private static class MyDesugarGraphConsumer implements DesugarGraphConsumer {
+
+      @Override
+      public void accept(Origin dependent, Origin dependency) {}
+
+      public void finished() {}
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java
new file mode 100644
index 0000000..b3258e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/sampleapi/R8ApiUsageSampleTest.java
@@ -0,0 +1,522 @@
+// Copyright (c) 2024, 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.compilerapi.sampleapi;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.AssertionsConfiguration;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+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.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * This API test is a copy over from the old API sample test set up.
+ *
+ * <p>NOTE: Don't use this test as the basis for new API tests. Instead, use one of the simpler and
+ * more feature directed tests found in the sibling packages.
+ */
+public class R8ApiUsageSampleTest extends CompilerApiTestRunner {
+
+  public R8ApiUsageSampleTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void test() throws IOException {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    test.run(temp);
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    private static final Origin origin =
+        new Origin(Origin.root()) {
+          @Override
+          public String part() {
+            return "R8ApiUsageSample";
+          }
+        };
+
+    private static final DiagnosticsHandler handler = new DiagnosticsHandler() {};
+
+    @Test
+    public void test() throws IOException {
+      run(temp);
+    }
+
+    public void run(TemporaryFolder temp) throws IOException {
+      Path pgConf = temp.getRoot().toPath().resolve("rules.conf");
+      Files.write(pgConf, getKeepMainRules(getMockClass()));
+      runFromArgs(
+          new String[] {
+            "--output",
+            temp.newFolder().getAbsolutePath(),
+            "--min-api",
+            "19",
+            "--pg-conf",
+            pgConf.toString(),
+            "--lib",
+            getAndroidJar().toString(),
+            getPathForClass(getMockClass()).toString()
+          });
+    }
+
+    public void runFromArgs(String[] args) {
+      // Check version API
+      checkVersionApi();
+      // Parse arguments with the commandline parser to make use of its API.
+      R8Command.Builder cmd = R8Command.parse(args, origin);
+      CompilationMode mode = cmd.getMode();
+      Path temp = cmd.getOutputPath();
+      int minApiLevel = cmd.getMinApiLevel();
+      // The Builder API does not provide access to the concrete paths
+      // (everything is put into providers) so manually parse them here.
+      List<Path> libraries = new ArrayList<>(1);
+      List<Path> pgConf = new ArrayList<>(1);
+      List<Path> inputs = new ArrayList<>(args.length);
+      for (int i = 0; i < args.length; i++) {
+        if (args[i].equals("--lib")) {
+          libraries.add(Paths.get(args[++i]));
+        } else if (args[i].equals("--pg-conf")) {
+          pgConf.add(Paths.get(args[++i]));
+        } else if (isArchive(args[i]) || isClassFile(args[i])) {
+          inputs.add(Paths.get(args[i]));
+        }
+      }
+      if (!Files.exists(temp) || !Files.isDirectory(temp)) {
+        throw new RuntimeException("Must supply a temp/output directory");
+      }
+      if (inputs.isEmpty()) {
+        throw new RuntimeException("Must supply program inputs");
+      }
+      if (libraries.isEmpty()) {
+        throw new RuntimeException("Must supply library inputs");
+      }
+      if (pgConf.isEmpty()) {
+        throw new RuntimeException("Must supply pg-conf inputs");
+      }
+
+      useProgramFileList(CompilationMode.DEBUG, minApiLevel, libraries, inputs);
+      useProgramFileList(CompilationMode.RELEASE, minApiLevel, libraries, inputs);
+      useProgramData(minApiLevel, libraries, inputs);
+      useProgramResourceProvider(minApiLevel, libraries, inputs);
+      useLibraryResourceProvider(minApiLevel, libraries, inputs);
+      useProguardConfigFiles(minApiLevel, libraries, inputs, pgConf);
+      useProguardConfigLines(minApiLevel, libraries, inputs, pgConf);
+      useAssertionConfig(minApiLevel, libraries, inputs);
+      useVArgVariants(minApiLevel, libraries, inputs, pgConf);
+      useProguardConfigConsumers(minApiLevel, libraries, inputs, pgConf);
+    }
+
+    private static class InMemoryStringConsumer implements StringConsumer {
+
+      public String value = null;
+
+      @Override
+      public void accept(String string, DiagnosticsHandler handler) {
+        value = string;
+      }
+    }
+
+    private static void useProguardConfigConsumers(
+        int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+      InMemoryStringConsumer usageConsumer = new InMemoryStringConsumer();
+      InMemoryStringConsumer seedsConsumer = new InMemoryStringConsumer();
+      InMemoryStringConsumer configConsumer = new InMemoryStringConsumer();
+      try {
+        R8.run(
+            R8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addProgramFiles(inputs)
+                .addProguardConfigurationFiles(pgConf)
+                .setProguardUsageConsumer(usageConsumer)
+                .setProguardSeedsConsumer(seedsConsumer)
+                .setProguardConfigurationConsumer(configConsumer)
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exception", e);
+      }
+      if (usageConsumer.value == null) {
+        throw new RuntimeException("Expected usage info but had none");
+      }
+      if (seedsConsumer.value == null) {
+        throw new RuntimeException("Expected seeds info but had none");
+      }
+      if (configConsumer.value == null) {
+        throw new RuntimeException("Expected config info but had none");
+      }
+    }
+
+    // Check API support for compiling Java class-files from the file system.
+    private static void useProgramFileList(
+        CompilationMode mode,
+        int minApiLevel,
+        Collection<Path> libraries,
+        Collection<Path> inputs) {
+      try {
+        R8.run(
+            R8Command.builder(handler)
+                .setMode(mode)
+                .setMinApiLevel(minApiLevel)
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addProgramFiles(inputs)
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    // Check API support for compiling Java class-files from byte content.
+    private static void useProgramData(
+        int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+      try {
+        R8Command.Builder builder =
+            R8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries);
+        for (ClassFileContent classfile : readClassFiles(inputs)) {
+          builder.addClassProgramData(classfile.data, classfile.origin);
+        }
+        R8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      } catch (IOException e) {
+        throw new RuntimeException("Unexpected IO exception", e);
+      }
+    }
+
+    // Check API support for compiling Java class-files from a program provider abstraction.
+    private static void useProgramResourceProvider(
+        int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+      try {
+        R8Command.Builder builder =
+            R8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries);
+        for (Path input : inputs) {
+          if (isArchive(input)) {
+            builder.addProgramResourceProvider(
+                ArchiveProgramResourceProvider.fromArchive(
+                    input, ArchiveProgramResourceProvider::includeClassFileEntries));
+          } else {
+            builder.addProgramResourceProvider(
+                new ProgramResourceProvider() {
+                  @Override
+                  public Collection<ProgramResource> getProgramResources()
+                      throws ResourceException {
+                    return Collections.singleton(ProgramResource.fromFile(Kind.CF, input));
+                  }
+                });
+          }
+        }
+        R8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    private static void useLibraryResourceProvider(
+        int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+      try {
+        R8Command.Builder builder =
+            R8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addProgramFiles(inputs);
+        for (Path library : libraries) {
+          builder.addLibraryResourceProvider(new ArchiveClassFileProvider(library));
+        }
+        R8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      } catch (IOException e) {
+        throw new RuntimeException("Unexpected IO exception", e);
+      }
+    }
+
+    private static void useProguardConfigFiles(
+        int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+      try {
+        R8.run(
+            R8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addProgramFiles(inputs)
+                .addProguardConfigurationFiles(pgConf)
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    private static void useProguardConfigLines(
+        int minApiLevel, Collection<Path> libraries, Collection<Path> inputs, List<Path> pgConf) {
+      try {
+        R8Command.Builder builder =
+            R8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addProgramFiles(inputs);
+        for (Path file : pgConf) {
+          builder.addProguardConfiguration(Files.readAllLines(file), new PathOrigin(file));
+        }
+        R8.run(builder.build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      } catch (IOException e) {
+        throw new RuntimeException("Unexpected IO exception", e);
+      }
+    }
+
+    private static void useAssertionConfig(
+        int minApiLevel, Collection<Path> libraries, Collection<Path> inputs) {
+      String pkg = "com.android.tools.r8.compilerapi.sampleapi";
+      try {
+        R8.run(
+            R8Command.builder(handler)
+                .setDisableTreeShaking(true)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries)
+                .addProgramFiles(inputs)
+                .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeEnable().build())
+                .addAssertionsConfiguration(b -> b.setScopeAll().setCompileTimeDisable().build())
+                .addAssertionsConfiguration(
+                    b -> b.setScopePackage(pkg).setCompileTimeEnable().build())
+                .addAssertionsConfiguration(b -> b.setScopePackage(pkg).setPassthrough().build())
+                .addAssertionsConfiguration(
+                    b -> b.setScopePackage(pkg).setCompileTimeDisable().build())
+                .addAssertionsConfiguration(
+                    b ->
+                        b.setScopeClass(pkg + "R8ApiUsageSampleTest")
+                            .setCompileTimeEnable()
+                            .build())
+                .addAssertionsConfiguration(
+                    b -> b.setScopeClass(pkg + "R8ApiUsageSampleTest").setPassthrough().build())
+                .addAssertionsConfiguration(
+                    b ->
+                        b.setScopeClass(pkg + "R8ApiUsageSampleTest")
+                            .setCompileTimeDisable()
+                            .build())
+                .addAssertionsConfiguration(
+                    AssertionsConfiguration.Builder::compileTimeEnableAllAssertions)
+                .addAssertionsConfiguration(
+                    AssertionsConfiguration.Builder::passthroughAllAssertions)
+                .addAssertionsConfiguration(
+                    AssertionsConfiguration.Builder::compileTimeDisableAllAssertions)
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    // Check API support for all the varg variants.
+    private static void useVArgVariants(
+        int minApiLevel, List<Path> libraries, List<Path> inputs, List<Path> pgConf) {
+      try {
+        R8.run(
+            R8Command.builder(handler)
+                .setMinApiLevel(minApiLevel)
+                .setProgramConsumer(new EnsureOutputConsumer())
+                .addLibraryFiles(libraries.get(0))
+                .addLibraryFiles(libraries.stream().skip(1).toArray(Path[]::new))
+                .addProgramFiles(inputs.get(0))
+                .addProgramFiles(inputs.stream().skip(1).toArray(Path[]::new))
+                .addProguardConfigurationFiles(pgConf.get(0))
+                .addProguardConfigurationFiles(pgConf.stream().skip(1).toArray(Path[]::new))
+                .build());
+      } catch (CompilationFailedException e) {
+        throw new RuntimeException("Unexpected compilation exceptions", e);
+      }
+    }
+
+    // Helpers for tests.
+    // Some of this reimplements stuff in R8 utils, but that is not public API and we should not
+    // rely on it.
+
+    private static List<ClassFileContent> readClassFiles(Collection<Path> files)
+        throws IOException {
+      List<ClassFileContent> classfiles = new ArrayList<>();
+      for (Path file : files) {
+        if (isArchive(file)) {
+          Origin zipOrigin = new PathOrigin(file);
+          ZipInputStream zip =
+              new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
+          ZipEntry entry;
+          while (null != (entry = zip.getNextEntry())) {
+            String name = entry.getName();
+            if (isClassFile(name)) {
+              Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
+              classfiles.add(new ClassFileContent(origin, readBytes(zip)));
+            }
+          }
+        } else if (isClassFile(file)) {
+          classfiles.add(new ClassFileContent(new PathOrigin(file), Files.readAllBytes(file)));
+        }
+      }
+      return classfiles;
+    }
+
+    private static byte[] readBytes(InputStream stream) throws IOException {
+      try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+        byte[] buffer = new byte[0xffff];
+        for (int length; (length = stream.read(buffer)) != -1; ) {
+          bytes.write(buffer, 0, length);
+        }
+        return bytes.toByteArray();
+      }
+    }
+
+    private static String toLowerCase(String str) {
+      return str.toLowerCase(Locale.ROOT);
+    }
+
+    private static boolean isClassFile(Path file) {
+      return isClassFile(file.toString());
+    }
+
+    private static boolean isClassFile(String file) {
+      file = toLowerCase(file);
+      return file.endsWith(".class");
+    }
+
+    private static boolean isArchive(Path file) {
+      return isArchive(file.toString());
+    }
+
+    private static boolean isArchive(String file) {
+      file = toLowerCase(file);
+      return file.endsWith(".zip") || file.endsWith(".jar");
+    }
+
+    private static class ClassFileContent {
+
+      final Origin origin;
+      final byte[] data;
+
+      public ClassFileContent(Origin origin, byte[] data) {
+        this.origin = origin;
+        this.data = data;
+      }
+    }
+
+    private static class EnsureOutputConsumer implements DexIndexedConsumer {
+
+      boolean hasOutput = false;
+
+      @Override
+      public synchronized void accept(
+          int fileIndex, byte[] data, Set<String> descriptors, DiagnosticsHandler handler) {
+        hasOutput = true;
+      }
+
+      @Override
+      public void finished(DiagnosticsHandler handler) {
+        if (!hasOutput) {
+          handler.error(new StringDiagnostic("Expected to produce output but had none"));
+        }
+      }
+    }
+
+    private static void checkVersionApi() {
+      String labelValue;
+      int labelAccess;
+      try {
+        Field field = Version.class.getDeclaredField("LABEL");
+        labelAccess = field.getModifiers();
+        labelValue = (String) field.get(Version.class);
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+      if (!Modifier.isPublic(labelAccess)
+          || !Modifier.isStatic(labelAccess)
+          || !Modifier.isFinal(labelAccess)) {
+        throw new RuntimeException("Expected public static final LABEL");
+      }
+      if (labelValue.isEmpty()) {
+        throw new RuntimeException("Expected LABEL constant");
+      }
+      if (Version.LABEL.isEmpty()) {
+        throw new RuntimeException("Expected LABEL constant");
+      }
+      if (Version.getVersionString() == null) {
+        throw new RuntimeException("Expected getVersionString API");
+      }
+      if (Version.getMajorVersion() < -1) {
+        throw new RuntimeException("Expected getMajorVersion API");
+      }
+      if (Version.getMinorVersion() < -1) {
+        throw new RuntimeException("Expected getMinorVersion API");
+      }
+      if (Version.getPatchVersion() < -1) {
+        throw new RuntimeException("Expected getPatchVersion API");
+      }
+      if (Version.getPreReleaseString() == null && false) {
+        throw new RuntimeException("Expected getPreReleaseString API");
+      }
+      if (Version.isDevelopmentVersion() && false) {
+        throw new RuntimeException("Expected isDevelopmentVersion API");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
index e7169c3..6175552 100644
--- a/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
+++ b/src/test/java/com/android/tools/r8/d8/ProguardMapSortByTest.java
@@ -51,7 +51,7 @@
             .addOptionsModification(
                 internalOptions -> internalOptions.testing.limitNumberOfClassesPerDex = 2);
     if (parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
-      d8TestBuilder.addMainDexListClasses(A.class);
+      d8TestBuilder.addMainDexRules("-keep class " + A.class.getName());
     }
     Path outputDir = temp.newFolder().toPath();
     d8TestBuilder
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
new file mode 100644
index 0000000..0747d96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import static org.junit.Assert.assertFalse;
+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.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApiLevelRangeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ApiLevelRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRange() {
+    ApiLevelRange rs = new ApiLevelRange(AndroidApiLevel.S, AndroidApiLevel.R);
+    ApiLevelRange op = new ApiLevelRange(AndroidApiLevel.P, AndroidApiLevel.O);
+    ApiLevelRange ir = new ApiLevelRange(AndroidApiLevel.R, AndroidApiLevel.I);
+    ApiLevelRange r = new ApiLevelRange(AndroidApiLevel.R, null);
+    ApiLevelRange i = new ApiLevelRange(AndroidApiLevel.I, null);
+
+    // Overlap with null.
+    assertTrue(i.overlap(r));
+    assertTrue(r.overlap(i));
+
+    // No overlap with null.
+    assertFalse(i.overlap(rs));
+    assertFalse(rs.overlap(i));
+
+    // No overlap.
+    assertFalse(rs.overlap(op));
+    assertFalse(op.overlap(rs));
+
+    // Partial overlap.
+    assertTrue(ir.overlap(rs));
+    assertTrue(rs.overlap(ir));
+
+    // Full overlap.
+    assertTrue(ir.overlap(op));
+    assertTrue(op.overlap(ir));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 5369238..0143e8e 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -209,12 +209,19 @@
             .compile()
             .writeToZip();
 
+    Path out3 =
+        testForD8()
+            .addProgramClasses(TestClass.class, MiniAssert.class)
+            .addClasspathClasses(CLASSES)
+            .setIntermediate(true)
+            .setMinApi(parameters)
+            .compile()
+            .writeToZip();
+
     MainDexConsumer mainDexConsumer = new MainDexConsumer();
-    List<Class<?>> classes = ImmutableList.of(TestClass.class, MiniAssert.class);
-    List<Path> files = ImmutableList.of(out1, out2);
-    GenerateMainDexListRunResult traceResult = traceMainDex(classes, files);
+    List<Path> files = ImmutableList.of(out1, out2, out3);
+    GenerateMainDexListRunResult traceResult = traceMainDex(Collections.emptyList(), files);
     testForD8()
-        .addProgramClasses(classes)
         .addProgramFiles(files)
         .setMinApi(parameters)
         .addMainDexListClassReferences(traceResult.getMainDexList())
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 1071a87..3f55e91 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -87,7 +87,7 @@
   @Test
   public void testBackportedMethodsPerAPILevel() throws Exception {
     for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
-      if (apiLevel == AndroidApiLevel.MASTER) {
+      if (apiLevel == AndroidApiLevel.MAIN) {
         continue;
       }
       if (apiLevel == AndroidApiLevel.U) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
index 0e9fe1e..60985a1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/StackWalkerTest.java
@@ -81,7 +81,7 @@
     testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
         .addProgramFiles(INPUT_JAR)
         .addKeepMainRule(MAIN_CLASS)
-        .overrideLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MASTER))
+        .overrideLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.MAIN))
         // Missing class java.lang.StackWalker$StackFrame.
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
         .run(parameters.getRuntime(), MAIN_CLASS)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java
index f150bbe..c32622e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/legacy/L8TestWithLegacySpecification.java
@@ -46,7 +46,7 @@
         Arrays.stream(AndroidApiLevel.values())
             .sorted()
             .filter(apiLevel -> apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L))
-            .filter(apiLevel -> apiLevel.isLessThan(AndroidApiLevel.MASTER))
+            .filter(apiLevel -> apiLevel.isLessThan(AndroidApiLevel.MAIN))
             .collect(Collectors.toList()),
         CompilationMode.values(),
         ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index 78a7fb7..62f069e 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -70,6 +70,16 @@
         .build();
   }
 
+  protected boolean hasTwrCloseResourceSupport(boolean isDesugaring) {
+    return !isDesugaring
+        || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport());
+  }
+
+  protected boolean hasTwrCloseResourceApiOutlines() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport());
+  }
+
   protected String getZipFile() throws IOException {
     return ZipUtils.ZipBuilder.builder(temp.newFile("file.zip").toPath())
         // DEX VMs from 4.4 up-to 9.0 including, will fail if no entry is added.
@@ -106,10 +116,13 @@
               // Throwable.addSuppressed that is still present in the original $closeResource.
               // TODO(b/214329923): If the original $closeResource is pruned this will decrease.
               // TODO(b/168568827): Once we support a nested addSuppressed this will increase.
-              int expectedSynthetics =
-                  parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
-                      ? 3
-                      : 0;
+              int expectedSynthetics = 0;
+              if (!hasTwrCloseResourceSupport(true)) {
+                expectedSynthetics += 2;
+              }
+              if (hasTwrCloseResourceApiOutlines()) {
+                expectedSynthetics += 1;
+              }
               assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
             });
   }
@@ -137,7 +150,7 @@
               // exception is known or not, thus the synthetic methods will be 2.
               Set<String> nonSyntheticClassOutput =
                   ImmutableSet.of(FOO.typeName(), BAR.typeName(), MAIN.typeName());
-              if (parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())) {
+              if (!hasTwrCloseResourceSupport(parameters.isDexRuntime())) {
                 Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput);
                 classOutputWithSynthetics.add(
                     SyntheticItemsTestUtils.syntheticApiOutlineClass(BAR.getClassReference(), 0)
diff --git a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
index ff2afbe..9ec7c0f 100644
--- a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
+++ b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
@@ -5,22 +5,19 @@
 package com.android.tools.r8.globalsynthetics;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.GlobalSyntheticsGenerator;
 import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.nio.file.Path;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Test;
@@ -31,91 +28,109 @@
 @RunWith(Parameterized.class)
 public class GlobalSyntheticsEnsureClassesOutputTest extends TestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  private final Backend backend;
+
+  @Parameters(name = "{0}, backend:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
   }
 
-  public GlobalSyntheticsEnsureClassesOutputTest(TestParameters parameters) {
+  public GlobalSyntheticsEnsureClassesOutputTest(TestParameters parameters, Backend backend) {
     parameters.assertNoneRuntime();
+    this.backend = backend;
   }
 
   @Test
   public void testNumberOfClassesOnK() throws Exception {
-    Path output = temp.newFolder().toPath().resolve("output.zip");
+    GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
     GlobalSyntheticsGenerator.run(
         GlobalSyntheticsGeneratorCommand.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
             .setMinApiLevel(AndroidApiLevel.K.getLevel())
-            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+            .setGlobalSyntheticsConsumer(globalsConsumer)
+            .setClassfileDesugaringOnly(backend.isCf())
             .build());
-    CodeInspector inspector = new CodeInspector(output);
-    assertEquals(1044, inspector.allClasses().size());
+    assertTrue(globalsConsumer.isSingleGlobal());
+    testForD8(backend)
+        .apply(
+            b ->
+                b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+        .setMinApi(AndroidApiLevel.K)
+        .compile()
+        .inspect(
+            inspector -> assertEquals(backend.isDex() ? 1045 : 4, inspector.allClasses().size()));
   }
 
   @Test
   public void testNumberOfClassesOnLatest() throws Exception {
-    Path output = temp.newFolder().toPath().resolve("output.zip");
+    GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
     GlobalSyntheticsGenerator.run(
         GlobalSyntheticsGeneratorCommand.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
             .setMinApiLevel(AndroidApiLevel.LATEST.getLevel())
-            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+            .setGlobalSyntheticsConsumer(globalsConsumer)
+            .setClassfileDesugaringOnly(backend.isCf())
             .build());
+    assertTrue(globalsConsumer.isSingleGlobal());
     Set<String> expectedInOutput = new HashSet<>();
     // The output contains a RecordTag type that is mapped back to the original java.lang.Record by
     // our codeinspector.
     expectedInOutput.add("Ljava/lang/Record;");
     expectedInOutput.add("Ljava/lang/invoke/VarHandle;");
     expectedInOutput.add("Ljava/lang/invoke/MethodHandles$Lookup;");
-    assertEquals(
-        expectedInOutput,
-        new CodeInspector(output)
-            .allClasses().stream()
-                .map(FoundClassSubject::getFinalDescriptor)
-                .collect(Collectors.toSet()));
+    testForD8(backend)
+        .apply(
+            b ->
+                b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compile()
+        .inspect(
+            inspector ->
+                assertEquals(
+                    expectedInOutput,
+                    inspector.allClasses().stream()
+                        .map(FoundClassSubject::getFinalDescriptor)
+                        .collect(Collectors.toSet())));
   }
 
   @Test
   public void testClassFileListOutput() throws Exception {
     Set<String> generatedGlobalSynthetics = SetUtils.newConcurrentHashSet();
-    Path output = temp.newFolder().toPath().resolve("output.zip");
+    GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
     runGlobalSyntheticsGenerator(
         GlobalSyntheticsGeneratorCommand.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
             .setMinApiLevel(AndroidApiLevel.K.getLevel())
-            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+            .setGlobalSyntheticsConsumer(globalsConsumer)
+            .setClassfileDesugaringOnly(backend.isCf())
             .build(),
         options ->
             options.testing.globalSyntheticCreatedCallback =
                 programClass -> {
-                  if (programClass
-                      .getClassReference()
-                      .getDescriptor()
-                      .equals(DexItemFactory.varHandleDescriptorString)) {
-                    // We emit a desugared var handle. Rewrite it here to allow checking for final
-                    // type names.
-                    generatedGlobalSynthetics.add(
-                        Reference.classFromDescriptor(
-                                DexItemFactory.desugarVarHandleDescriptorString)
-                            .getTypeName());
-                  } else if (programClass
-                      .getClassReference()
-                      .getDescriptor()
-                      .equals(DexItemFactory.methodHandlesLookupDescriptorString)) {
-                    // We emit a desugared var handle. Rewrite it here to allow checking for final
-                    // type names.
-                    generatedGlobalSynthetics.add(
-                        Reference.classFromDescriptor(
-                                DexItemFactory.desugarMethodHandlesLookupDescriptorString)
-                            .getTypeName());
-                  } else {
-                    generatedGlobalSynthetics.add(programClass.getTypeName());
-                  }
+                  String descriptor = programClass.getClassReference().getDescriptor();
+                  generatedGlobalSynthetics.add(
+                      descriptor
+                          .replace(
+                              "Ljava/lang/invoke/VarHandle",
+                              "Lcom/android/tools/r8/DesugarVarHandle")
+                          .replace(
+                              "Ljava/lang/invoke/MethodHandles$Lookup",
+                              "Lcom/android/tools/r8/DesugarMethodHandlesLookup"));
                 });
-    Set<String> readGlobalSynthetics =
-        new CodeInspector(output)
-            .allClasses().stream().map(FoundClassSubject::getFinalName).collect(Collectors.toSet());
-    assertEquals(generatedGlobalSynthetics, readGlobalSynthetics);
+    assertTrue(globalsConsumer.isSingleGlobal());
+    testForD8()
+        .apply(
+            b ->
+                b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+        .setMinApi(AndroidApiLevel.K)
+        .compile()
+        .inspect(
+            inspector -> {
+              Set<String> readGlobalSynthetics =
+                  inspector.allClasses().stream()
+                      .map(FoundClassSubject::getFinalDescriptor)
+                      .collect(Collectors.toSet());
+              assertEquals(generatedGlobalSynthetics, readGlobalSynthetics);
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index bc561cf..4c61934 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -70,58 +70,12 @@
   }
 
   @Test
-  public void testD8DebugLegacyMultidex() throws Exception {
-    compileWithD8Debug(
-            builder ->
-                builder
-                    .addMainDexListFiles(base.resolve("main_dex_list.txt"))
-                    .setMinApi(AndroidApiLevel.K))
-        .runDex2Oat(parameters.getRuntime())
-        .assertNoVerificationErrors();
-  }
-
-  @Test
-  public void testD8DebugLegacyMultidexDexOpt() throws Exception {
-    compileWithD8Debug(
-            builder ->
-                builder
-                    .addMainDexListFiles(base.resolve("main_dex_list.txt"))
-                    .setMinApi(AndroidApiLevel.K)
-                    .setOptimizeMultidexForLinearAlloc())
-        .runDex2Oat(parameters.getRuntime())
-        .assertNoVerificationErrors();
-  }
-
-  @Test
   public void testD8Release() throws Exception {
     compileWithD8Release(ThrowableConsumer.empty())
         .runDex2Oat(parameters.getRuntime())
         .assertNoVerificationErrors();
   }
 
-  @Test
-  public void testD8ReleaseLegacyMultidex() throws Exception {
-    compileWithD8Release(
-            builder ->
-                builder
-                    .addMainDexListFiles(base.resolve("main_dex_list.txt"))
-                    .setMinApi(AndroidApiLevel.K))
-        .runDex2Oat(parameters.getRuntime())
-        .assertNoVerificationErrors();
-  }
-
-  @Test
-  public void buildD8ReleaseLegacyMultidexDexOpt() throws Exception {
-    compileWithD8Release(
-            builder ->
-                builder
-                    .addMainDexListFiles(base.resolve("main_dex_list.txt"))
-                    .setMinApi(AndroidApiLevel.K)
-                    .setOptimizeMultidexForLinearAlloc())
-        .runDex2Oat(parameters.getRuntime())
-        .assertNoVerificationErrors();
-  }
-
   private D8TestCompileResult compileWithD8Debug(ThrowableConsumer<D8TestBuilder> configuration)
       throws Exception {
     return compileWithD8(configuration.andThen(TestCompilerBuilder::debug));
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
deleted file mode 100644
index 4929cb2..0000000
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.internal;
-
-import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Paths;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class Gmail18082615TreeShakeJarVerificationTest extends GmailCompilationBase {
-  private static final int MAX_SIZE = 20000000;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public Gmail18082615TreeShakeJarVerificationTest(TestParameters parameters) {
-    super(180826, 15);
-    parameters.assertNoneRuntime();
-  }
-
-  @Test
-  public void buildAndTreeShakeFromDeployJar() throws Exception {
-    assumeTrue(isLocalDevelopment());
-
-    R8TestCompileResult compileResult =
-        testForR8(Backend.DEX)
-            .addKeepRuleFiles(
-                Paths.get(base).resolve(BASE_PG_CONF),
-                Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS, PG_CONF))
-            .allowDiagnosticMessages()
-            .allowUnusedProguardConfigurationRules()
-            .compile()
-            .assertAllInfoMessagesMatch(
-                anyOf(
-                    equalTo("Ignoring option: -optimizations"),
-                    containsString("Proguard configuration rule does not match anything"),
-                    containsString("Invalid parameter counts in MethodParameter attributes"),
-                    containsString("Methods with invalid MethodParameter attributes")))
-            .assertAllWarningMessagesMatch(containsString("Ignoring option:"));
-
-    int appSize = compileResult.app.applicationSize();
-    assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java b/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java
deleted file mode 100644
index 7332dd8..0000000
--- a/src/test/java/com/android/tools/r8/internal/GmailCompilationBase.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.internal;
-
-abstract class GmailCompilationBase extends CompilationTestBase {
-  static final String APK = "Gmail_release_unsigned.apk";
-  static final String DEPLOY_JAR = "Gmail_release_unstripped_deploy.jar";
-  static final String PG_JAR = "Gmail_release_unstripped_proguard.jar";
-  static final String PG_MAP = "Gmail_release_unstripped_proguard.map";
-  static final String BASE_PG_CONF = "Gmail_release_unstripped_proguard.config";
-  static final String PG_CONF = "Gmail_proguard.config";
-
-  final String base;
-
-  GmailCompilationBase(int majorVersion, int minorVersion) {
-    this.base = "third_party/gmail/gmail_android_" + majorVersion + "." + minorVersion + "/";
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java b/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java
new file mode 100644
index 0000000..cd41b8d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/ComposeBinopTest.java
@@ -0,0 +1,319 @@
+// Copyright (c) 2024, 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ComposeBinopTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "12345678",
+          "12345678",
+          "0",
+          "0",
+          "76532",
+          "76532",
+          "0",
+          "0",
+          "-2147483648",
+          "-2147483648",
+          "0",
+          "0",
+          "74",
+          "0",
+          "12345838",
+          "12345854",
+          "240",
+          "160",
+          "76532",
+          "76542",
+          "0",
+          "0",
+          "-2147483488",
+          "-2147483398",
+          "8260",
+          "0",
+          "12345678",
+          "-2135069698",
+          "8260",
+          "0",
+          "76532",
+          "-2135069698",
+          "0",
+          "0",
+          "-2147475388",
+          "-2135069698",
+          "0",
+          "0",
+          "3948544",
+          "1572864",
+          "0",
+          "0",
+          "0",
+          "0",
+          "252706816",
+          "100663296",
+          "0",
+          "0",
+          "255",
+          "80",
+          "241",
+          "96",
+          "243",
+          "96",
+          "1551743",
+          "1032",
+          "99311600",
+          "66080",
+          "1551743",
+          "1032",
+          "-266892247",
+          "0",
+          "98765424",
+          "0",
+          "269978665",
+          "0",
+          "-268425890",
+          "0",
+          "612256",
+          "0",
+          "268445022",
+          "0",
+          "12413950",
+          "8260",
+          "12413950",
+          "8260",
+          "12413950",
+          "8260",
+          "-131068",
+          "0",
+          "1253900288",
+          "0",
+          "131076",
+          "0",
+          "-2037",
+          "0",
+          "350224384",
+          "0",
+          "2059",
+          "0",
+          "41",
+          "350",
+          "0");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ComposeBinopTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject mainClass = inspector.clazz(Main.class);
+    assertTrue(mainClass.isPresent());
+
+    assertTrue(
+        mainClass
+            .uniqueMethodWithOriginalName("bitSameInput")
+            .streamInstructions()
+            .noneMatch(InstructionSubject::isIntLogicalBinop));
+    assertEquals(
+        6,
+        mainClass
+            .uniqueMethodWithOriginalName("shareShiftCstLeft")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        12,
+        mainClass
+            .uniqueMethodWithOriginalName("shareShiftCstRight")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        12,
+        mainClass
+            .uniqueMethodWithOriginalName("shareShiftVar")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        4,
+        mainClass
+            .uniqueMethodWithOriginalName("andOrCompositionCst")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        8,
+        mainClass
+            .uniqueMethodWithOriginalName("andOrCompositionVar")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+    assertEquals(
+        2,
+        mainClass
+            .uniqueMethodWithOriginalName("composeTests")
+            .streamInstructions()
+            .filter(InstructionSubject::isIntLogicalBinop)
+            .count());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int i1 = System.currentTimeMillis() > 0 ? 12345678 : 1;
+      int i2 = System.currentTimeMillis() > 0 ? 76532 : 1;
+      int i3 = System.currentTimeMillis() > 0 ? Integer.MIN_VALUE : 1;
+
+      bitSameInput(i1);
+      bitSameInput(i2);
+      bitSameInput(i3);
+
+      andOrCompositionCst(i1);
+      andOrCompositionCst(i2);
+      andOrCompositionCst(i3);
+
+      andOrCompositionVar(i1, i2, i3);
+      andOrCompositionVar(i2, i3, i1);
+      andOrCompositionVar(i3, i1, i2);
+
+      shareShiftCstLeft(i1);
+      shareShiftCstLeft(i2);
+      shareShiftCstLeft(i3);
+
+      shareShiftCstRight(i1, i2);
+      shareShiftCstRight(i1, i3);
+      shareShiftCstRight(i2, i3);
+
+      shareShiftVar(i1, i2, i3);
+      shareShiftVar(i2, i3, i1);
+      shareShiftVar(i3, i1, i2);
+
+      composeTests(i1);
+      composeTests(i2);
+      composeTests(i3);
+    }
+
+    @NeverInline
+    private static void bitSameInput(int a) {
+      // a & a => a, a | a => a.
+      System.out.println(a & a);
+      System.out.println(a | a);
+      // a ^ a => 0, a - a => 0
+      System.out.println(a ^ a);
+      System.out.println(a - a);
+    }
+
+    @NeverInline
+    private static void shareShiftCstRight(int a, int b) {
+      // (x shift: val) | (y shift: val) => (x | y) shift: val.
+      // (x shift: val) & (y shift: val) => (x & y) shift: val.
+      System.out.println((a >> 3) | (b >> 3));
+      System.out.println((a >> 3) & (b >> 3));
+      System.out.println((a << 3) | (b << 3));
+      System.out.println((a << 3) & (b << 3));
+      System.out.println((a >>> 3) | (b >>> 3));
+      System.out.println((a >>> 3) & (b >>> 3));
+    }
+
+    @NeverInline
+    private static void shareShiftCstLeft(int a) {
+      // (x shift: val) | (y shift: val) => (x | y) shift: val.
+      // (x shift: val) & (y shift: val) => (x & y) shift: val.
+      System.out.println((111 >> a) | (222 >> a));
+      System.out.println((112 >> a) & (223 >> a));
+      System.out.println((113 << a) | (224 << a));
+      System.out.println((114 << a) & (225 << a));
+      System.out.println((115 >>> a) | (226 >>> a));
+      System.out.println((116 >>> a) & (227 >>> a));
+    }
+
+    @NeverInline
+    private static void shareShiftVar(int a, int b, int c) {
+      // (x shift: val) | (y shift: val) => (x | y) shift: val.
+      // (x shift: val) & (y shift: val) => (x & y) shift: val.
+      System.out.println((a >> c) | (b >> c));
+      System.out.println((a >> c) & (b >> c));
+      System.out.println((a << c) | (b << c));
+      System.out.println((a << c) & (b << c));
+      System.out.println((a >>> c) | (b >>> c));
+      System.out.println((a >>> c) & (b >>> c));
+    }
+
+    @NeverInline
+    private static void andOrCompositionCst(int a) {
+      // For all permutations of & and |, represented by &| and |&.
+      // (x &| a) |& (x &| b) => x &| (a |& b).
+      // Note that here a and b are constants therefore should be resolved at compile-time.
+      System.out.println((a & 0b10101010) | (a & 0b11110000));
+      System.out.println((a & 0b10101010) & (a & 0b11110000));
+      System.out.println((a | 0b10101010) & (a | 0b11110000));
+      System.out.println((a | 0b10101010) | (a | 0b11110000));
+    }
+
+    @NeverInline
+    private static void andOrCompositionVar(int a, int b, int c) {
+      // For all permutations of & and |.
+      // (x &| a) |& (x &| b) => x &| (a |& b).
+      System.out.println((a & b) | (a & c));
+      System.out.println((a & b) & (a & c));
+      System.out.println((a | b) & (a | c));
+      System.out.println((a | b) | (a | c));
+    }
+
+    @NeverInline
+    private static void composeTests(int dirty) {
+      // This is rewritten to ((0b0001111111111110 & dirty) >> 3).
+      System.out.println(
+          ((0b1110 & dirty) >> 3)
+              | ((0b01110000 & dirty) >> 3)
+              | ((0b001110000000 & dirty) >> 3)
+              | ((0b0001110000000000 & dirty) >> 3));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingBoolTest.java b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingBoolTest.java
new file mode 100644
index 0000000..cda7825
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IdentityAbsorbingBoolTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2024, 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;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdentityAbsorbingBoolTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "true", "true", "true", "true", "true", "true", "true", "true", "true", "true", "true",
+          "true", "true", "true", "true", "false", "false", "false", "true", "true", "true",
+          "false", "false", "true", "true", "false", "false", "false", "false", "false", "false",
+          "false", "false", "false", "false", "false", "false", "false", "false", "false", "false",
+          "false", "false", "true", "true", "true", "false", "false", "true", "true");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public IdentityAbsorbingBoolTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    inspector
+        .clazz(Main.class)
+        .forAllMethods(
+            m ->
+                assertTrue(
+                    m.streamInstructions()
+                        .noneMatch(i -> i.isIntLogicalBinop() || i.isIntArithmeticBinop())));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      boolTests(System.currentTimeMillis() > 0);
+      boolTests(System.currentTimeMillis() < 0);
+    }
+
+    private static void boolTests(boolean val) {
+      identityDoubleBoolTest(val);
+      identityBoolTest(val);
+      absorbingDoubleBoolTest(val);
+      absorbingBoolTest(val);
+    }
+
+    @NeverInline
+    private static void identityDoubleBoolTest(boolean val) {
+      System.out.println(val & true & true);
+      System.out.println(true & val & true);
+      System.out.println(true & true & val);
+      System.out.println(val | false | false);
+      System.out.println(false | val | false);
+      System.out.println(false | false | val);
+      System.out.println(val ^ false ^ false);
+      System.out.println(false ^ val ^ false);
+      System.out.println(false ^ false ^ val);
+    }
+
+    @NeverInline
+    private static void identityBoolTest(boolean val) {
+      System.out.println(val & true);
+      System.out.println(true & val);
+      System.out.println(val | false);
+      System.out.println(false | val);
+      System.out.println(val ^ false);
+      System.out.println(false ^ val);
+    }
+
+    @NeverInline
+    private static void absorbingDoubleBoolTest(boolean val) {
+      System.out.println(false & false & val);
+      System.out.println(false & val & false);
+      System.out.println(val & false & false);
+      System.out.println(true | true | val);
+      System.out.println(true | val | true);
+      System.out.println(val | true | true);
+    }
+
+    @NeverInline
+    private static void absorbingBoolTest(boolean val) {
+      System.out.println(false & val);
+      System.out.println(val & false);
+      System.out.println(true | val);
+      System.out.println(val | true);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
index b9c3c87..551e1b0 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
@@ -99,7 +99,12 @@
     TestConsumer programConsumer = new TestConsumer();
     testForD8()
         .setMinApi(AndroidApiLevel.L)
-        .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+        .addProgramFiles(
+            testForD8()
+                .addProgramClasses(TestClassA.class, TestClassB.class)
+                .setMinApi(AndroidApiLevel.L)
+                .compile()
+                .writeToZip())
         .addMainDexListClasses(TestClassB.class)
         .setProgramConsumer(programConsumer)
         .compile();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
index 7576780..29f2f79 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
@@ -80,19 +80,6 @@
         });
   }
 
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void testMainDexClasses() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
-    runTest(
-        r8FullTestBuilder ->
-            r8FullTestBuilder
-                .addMainDexListClasses(I.class, Provider.class, Main.class)
-                .allowDiagnosticWarningMessages(),
-        this::inspect);
-  }
-
   @Test
   public void testMainDexTracing() throws Exception {
     assumeTrue(parameters.isDexRuntime());
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
deleted file mode 100644
index 0b04ddc..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
-import org.junit.BeforeClass;
-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 MainDexListFromGenerateMainDexInliningSpuriousRootTest extends TestBase {
-
-  private static List<ClassReference> mainDexList;
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntimes()
-        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-        .build();
-  }
-
-  @BeforeClass
-  public static void setup() throws Exception {
-    mainDexList =
-        testForMainDexListGenerator(getStaticTemp())
-            .addInnerClasses(MainDexListFromGenerateMainDexInliningSpuriousRootTest.class)
-            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-            .addMainDexRules(
-                "-keep class " + Main.class.getTypeName() + " {",
-                "  public static void main(java.lang.String[]);",
-                "}")
-            .run()
-            .getMainDexList();
-  }
-
-  public MainDexListFromGenerateMainDexInliningSpuriousRootTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void test() throws Exception {
-    // The generated main dex list should contain Main (which is a root) and A (which is a direct
-    // dependency of Main).
-    assertEquals(2, mainDexList.size());
-    assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
-    assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .addInliningAnnotations()
-            .addKeepClassAndMembersRules(Main.class)
-            .addKeepMainRule(Main2.class)
-            .addMainDexListClassReferences(mainDexList)
-            .addMainDexRules(
-                "-keep class " + Main2.class.getTypeName() + " {",
-                "  public static void main(java.lang.String[]);",
-                "}")
-            .collectMainDexClasses()
-            .enableInliningAnnotations()
-            .enableNoHorizontalClassMergingAnnotations()
-            .setMinApi(parameters)
-            .addDontObfuscate()
-            .allowDiagnosticMessages()
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics
-                        .assertOnlyWarnings()
-                        .assertWarningsMatch(
-                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-    CodeInspector inspector = compileResult.inspector();
-    ClassSubject mainClassSubject = inspector.clazz(Main.class);
-    assertThat(mainClassSubject, isPresent());
-    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithOriginalName("foo");
-    assertThat(fooMethodSubject, isPresent());
-    ClassSubject main2ClassSubject = inspector.clazz(Main2.class);
-    assertThat(main2ClassSubject, isPresent());
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-    MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithOriginalName("bar");
-    assertThat(barMethodSubject, isPresent());
-    ClassSubject bClassSubject = inspector.clazz(B.class);
-    assertThat(bClassSubject, isPresent());
-    MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithOriginalName("baz");
-    assertThat(bazMethodSubject, isPresent());
-    assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
-    assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
-    assertEquals(
-        ImmutableSet.of(
-            mainClassSubject.getFinalName(),
-            main2ClassSubject.getFinalName(),
-            aClassSubject.getFinalName()),
-        compileResult.getMainDexClasses());
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      System.out.println("Main.main()");
-    }
-
-    static void foo() {
-      A.bar();
-    }
-  }
-
-  static class Main2 {
-
-    public static void main(String[] args) {
-      if (getFalse()) {
-        A.bar();
-      }
-    }
-
-    static boolean getFalse() {
-      return false;
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class A {
-    // Must not be inlined into Main.foo(), since that would cause B to become direct dependence of
-    // Main without ending up in the main dex.
-    static void bar() {
-      B.baz();
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class B {
-
-    @NeverInline
-    static void baz() {
-      System.out.println("B.baz");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
deleted file mode 100644
index 5c61a78..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
-import org.junit.BeforeClass;
-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 MainDexListFromGenerateMainDexInliningTest extends TestBase {
-
-  private static List<ClassReference> mainDexList;
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntimes()
-        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-        .build();
-  }
-
-  @BeforeClass
-  public static void setup() throws Exception {
-    mainDexList =
-        testForMainDexListGenerator(getStaticTemp())
-            .addInnerClasses(MainDexListFromGenerateMainDexInliningTest.class)
-            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-            .addMainDexRules(
-                "-keep class " + Main.class.getTypeName() + " {",
-                "  public static void main(java.lang.String[]);",
-                "}")
-            .run()
-            .getMainDexList();
-  }
-
-  public MainDexListFromGenerateMainDexInliningTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void test() throws Exception {
-    // The generated main dex list should contain Main (which is a root) and A (which is a direct
-    // dependency of Main).
-    assertEquals(2, mainDexList.size());
-    assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
-    assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
-
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .addInliningAnnotations()
-            .addKeepClassAndMembersRules(Main.class)
-            .addMainDexListClassReferences(mainDexList)
-            .collectMainDexClasses()
-            .enableInliningAnnotations()
-            .enableNoHorizontalClassMergingAnnotations()
-            .enableNoHorizontalClassMergingAnnotations()
-            .setMinApi(parameters)
-            .allowDiagnosticMessages()
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics
-                        .assertOnlyWarnings()
-                        .assertWarningsMatch(
-                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
-    CodeInspector inspector = compileResult.inspector();
-    ClassSubject mainClassSubject = inspector.clazz(Main.class);
-    assertThat(mainClassSubject, isPresent());
-
-    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithOriginalName("foo");
-    assertThat(fooMethodSubject, isPresent());
-
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-
-    MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithOriginalName("bar");
-    assertThat(barMethodSubject, isPresent());
-
-    ClassSubject bClassSubject = inspector.clazz(B.class);
-    assertThat(bClassSubject, isPresent());
-
-    MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithOriginalName("baz");
-    assertThat(bazMethodSubject, isPresent());
-
-    assertThat(fooMethodSubject, invokesMethod(barMethodSubject));
-    assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
-
-    // The main dex classes should be the same as the input main dex list.
-    assertEquals(
-        ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
-        compileResult.getMainDexClasses());
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      System.out.println("Main.main()");
-    }
-
-    static void foo() {
-      // Should not allow inlining bar into foo(), since that adds B as a direct
-      // dependence, and we don't include the direct dependencies of main dex list classes.
-      A.bar();
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class A {
-
-    static void bar() {
-      B.baz();
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class B {
-
-    @NeverInline
-    static void baz() {
-      System.out.println("B.baz");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
deleted file mode 100644
index 5a4d15c..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
-import org.junit.BeforeClass;
-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 MainDexListFromGenerateMainDexInliningWithTracingTest extends TestBase {
-
-  private static List<ClassReference> mainDexList;
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntimes()
-        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-        .build();
-  }
-
-  @BeforeClass
-  public static void setup() throws Exception {
-    mainDexList =
-        testForMainDexListGenerator(getStaticTemp())
-            .addInnerClasses(MainDexListFromGenerateMainDexInliningWithTracingTest.class)
-            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-            .addMainDexRules(
-                "-keep class " + Main.class.getTypeName() + " {",
-                "  public static void main(java.lang.String[]);",
-                "}")
-            .run()
-            .getMainDexList();
-  }
-
-  public MainDexListFromGenerateMainDexInliningWithTracingTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void test() throws Exception {
-    // The generated main dex list should contain Main (which is a root) and A (which is a direct
-    // dependency of Main).
-    assertEquals(2, mainDexList.size());
-    assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
-    assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
-
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .addInliningAnnotations()
-            .addKeepClassAndMembersRules(Main.class)
-            .addMainDexListClassReferences(mainDexList)
-            .addMainDexRules(
-                "-keep class " + Main.class.getTypeName() + " {",
-                "  public static void foo(java.lang.String[]);",
-                "}")
-            .collectMainDexClasses()
-            .enableInliningAnnotations()
-            .enableNoHorizontalClassMergingAnnotations()
-            .addDontObfuscate()
-            .setMinApi(parameters)
-            .allowDiagnosticMessages()
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics
-                        .assertOnlyWarnings()
-                        .assertWarningsMatch(
-                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
-    CodeInspector inspector = compileResult.inspector();
-    ClassSubject mainClassSubject = inspector.clazz(Main.class);
-    assertThat(mainClassSubject, isPresent());
-
-    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithOriginalName("foo");
-    assertThat(fooMethodSubject, isPresent());
-
-    MethodSubject notCalledAtStartupMethodSubject =
-        mainClassSubject.uniqueMethodWithOriginalName("notCalledAtStartup");
-    assertThat(notCalledAtStartupMethodSubject, isPresent());
-
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-
-    MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithOriginalName("bar");
-    assertThat(barMethodSubject, isPresent());
-
-    ClassSubject bClassSubject = inspector.clazz(B.class);
-    assertThat(bClassSubject, isPresent());
-
-    MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithOriginalName("baz");
-    assertThat(bazMethodSubject, isPresent());
-
-    assertThat(notCalledAtStartupMethodSubject, invokesMethod(barMethodSubject));
-    assertThat(barMethodSubject, invokesMethod(bazMethodSubject));
-
-    // The main dex classes should be the same as the input main dex list.
-    assertEquals(
-        ImmutableSet.of(mainClassSubject.getFinalName(), aClassSubject.getFinalName()),
-        compileResult.getMainDexClasses());
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      System.out.println("Main.main()");
-    }
-
-    public static void notCalledAtStartup() {
-      // Should not allow inlining bar into notCalledAtStartup(), since that adds B as a direct
-      // dependence, and we don't include the direct dependencies of main dex list classes.
-      new A().bar();
-    }
-
-    // This method is traced for main dex when running with R8, to add A as a dependency.
-    static A foo(A a) {
-      if (a != null) {
-        System.out.println("Hello World");
-      }
-      return a;
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class A {
-
-    static void bar() {
-      B.baz();
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class B {
-
-    @NeverInline
-    static void baz() {
-      System.out.println("B.baz");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
index 97d9c79..0125e03 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
@@ -38,7 +38,6 @@
 @RunWith(Parameterized.class)
 public class MainDexListFromGenerateMainHorizontalMergingTest extends TestBase {
 
-  private static List<ClassReference> mainDexList;
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
@@ -49,36 +48,10 @@
         .build();
   }
 
-  @BeforeClass
-  public static void setup() throws Exception {
-    mainDexList =
-        testForMainDexListGenerator(getStaticTemp())
-            .addInnerClasses(MainDexListFromGenerateMainHorizontalMergingTest.class)
-            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-            .addMainDexRules(
-                "-keep class " + Main.class.getTypeName() + " { public static void foo(); }")
-            .run()
-            .getMainDexList();
-  }
-
   public MainDexListFromGenerateMainHorizontalMergingTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void testMainDexList() throws Exception {
-    assertEquals(3, mainDexList.size());
-    Set<String> mainDexReferences =
-        mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
-    assertTrue(mainDexReferences.contains(A.class.getTypeName()));
-    assertTrue(mainDexReferences.contains(B.class.getTypeName()));
-    assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
-    runTest(
-        builder ->
-            builder.addMainDexListClassReferences(mainDexList).allowDiagnosticWarningMessages());
-  }
-
   @Test
   public void testMainDexTracing() throws Exception {
     runTest(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
index 4dba7f0..6ad455d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
@@ -37,8 +37,6 @@
 @RunWith(Parameterized.class)
 public class MainDexListFromGenerateMainVerticalMergingTest extends TestBase {
 
-  private static List<ClassReference> mainDexList;
-
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
@@ -49,36 +47,10 @@
         .build();
   }
 
-  @BeforeClass
-  public static void setup() throws Exception {
-    mainDexList =
-        testForMainDexListGenerator(getStaticTemp())
-            .addInnerClasses(MainDexListFromGenerateMainVerticalMergingTest.class)
-            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-            .addMainDexRules(
-                "-keep class " + Main.class.getTypeName() + " { public static void foo(); }")
-            .run()
-            .getMainDexList();
-  }
-
   public MainDexListFromGenerateMainVerticalMergingTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void testMainDexList() throws Exception {
-    assertEquals(3, mainDexList.size());
-    Set<String> mainDexReferences =
-        mainDexList.stream().map(TypeReference::getTypeName).collect(Collectors.toSet());
-    assertTrue(mainDexReferences.contains(A.class.getTypeName()));
-    assertTrue(mainDexReferences.contains(B.class.getTypeName()));
-    assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
-    runTest(
-        builder ->
-            builder.addMainDexListClassReferences(mainDexList).allowDiagnosticWarningMessages());
-  }
-
   @Test
   public void testMainTracing() throws Exception {
     runTest(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
deleted file mode 100644
index 5e24bcf..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class MainDexListInliningTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntimes()
-        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-        .build();
-  }
-
-  public MainDexListInliningTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  // TODO(b/181858113): This test should be converted to a main-dex-rules test.
-  @Test
-  public void test() throws Exception {
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .addKeepMainRule(Main.class)
-            .addMainDexListClasses(Main.class)
-            .collectMainDexClasses()
-            .enableNoHorizontalClassMergingAnnotations()
-            .setMinApi(parameters)
-            .allowDiagnosticMessages()
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics
-                        .assertOnlyWarnings()
-                        .assertWarningsMatch(
-                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
-    CodeInspector inspector = compileResult.inspector();
-
-    ClassSubject mainClassSubject = inspector.clazz(Main.class);
-    assertThat(mainClassSubject, isPresent());
-
-    // A is not allowed to be inlined and is therefore present.
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-
-    // B should be referenced from Main.main.
-    ClassSubject bClassSubject = inspector.clazz(B.class);
-    assertThat(bClassSubject, isPresent());
-
-    compileResult.inspectMainDexClasses(
-        mainDexClasses -> {
-          assertTrue(mainDexClasses.contains(mainClassSubject.getFinalName()));
-          // Since we passed a main-dex list the traced references A and B are not automagically
-          // included.
-          assertFalse(mainDexClasses.contains(aClassSubject.getFinalName()));
-          assertFalse(mainDexClasses.contains(bClassSubject.getFinalName()));
-        });
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      // Should be inlined.
-      A.m();
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class A {
-
-    public static void m() {
-      System.out.println(B.class);
-    }
-  }
-
-  @NoHorizontalClassMerging
-  static class B {}
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
deleted file mode 100644
index a26fe55..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.maindexlist;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class MainDexListNoDirectDependenciesTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntimes()
-        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-        .build();
-  }
-
-  public MainDexListNoDirectDependenciesTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void test() throws Exception {
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .addMainDexListClasses(A.class)
-            .addMainDexKeepClassRules(B.class)
-            .collectMainDexClasses()
-            .noTreeShaking()
-            .setMinApi(parameters)
-            .allowDiagnosticMessages()
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics
-                        .assertOnlyWarnings()
-                        .assertWarningsMatch(
-                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
-
-    CodeInspector inspector = compileResult.inspector();
-
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    ClassSubject referencedFromAClassSubject = inspector.clazz(ReferencedFromA.class);
-    ClassSubject bClassSubject = inspector.clazz(B.class);
-    ClassSubject referencedFromBClassSubject = inspector.clazz(ReferencedFromB.class);
-
-    compileResult.inspectMainDexClasses(
-        mainDexClasses -> {
-          assertTrue(mainDexClasses.contains(aClassSubject.getFinalName()));
-          // It is assumed that the provided main dex list includes its direct dependencies.
-          // Therefore, we explicitly do not include the direct dependencies of the main dex list
-          // classes in the final main dex, since this would lead to the dependencies of the
-          // dependencies being included in the main dex.
-          assertFalse(mainDexClasses.contains(referencedFromAClassSubject.getFinalName()));
-          assertTrue(mainDexClasses.contains(bClassSubject.getFinalName()));
-          assertTrue(mainDexClasses.contains(referencedFromBClassSubject.getFinalName()));
-        });
-  }
-
-  static class A {
-
-    public void m() {
-      System.out.println(ReferencedFromA.class);
-    }
-  }
-
-  static class ReferencedFromA {}
-
-  static class B {
-
-    public void m() {
-      System.out.println(ReferencedFromB.class);
-    }
-  }
-
-  static class ReferencedFromB {}
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 0da2423..98a3739 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -10,16 +10,14 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
+import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -566,16 +564,14 @@
           originalInspector.clazz(clazz).isPresent());
     }
     Path outDir = temp.newFolder().toPath();
-    R8Command.Builder builder =
-        R8Command.builder(handler)
+    D8Command.Builder builder =
+        D8Command.builder(handler)
             .addProgramFiles(app)
             .setMode(
                 minimalMainDex && mainDex.size() > 0
                     ? CompilationMode.DEBUG
                     : CompilationMode.RELEASE)
-            .setOutput(outDir, OutputMode.DexIndexed)
-            .setDisableTreeShaking(true)
-            .setDisableMinification(true);
+            .setOutput(outDir, OutputMode.DexIndexed);
 
     switch (testMode) {
       case SINGLE_FILE:
@@ -619,7 +615,7 @@
       }
     }
 
-    ToolHelper.runR8(builder.build());
+    D8.run(builder.build());
     if (!singleDexApp && !minimalMainDex) {
       assertTrue("Output run only produced one dex file.",
           1 < Files.list(outDir).filter(FileUtils::isDexFile).count());
@@ -671,7 +667,9 @@
 
   private static void generateApplication(Path output, List<String> classes, int methodCount)
       throws IOException {
-    ArchiveConsumer consumer = new ArchiveConsumer(output);
+    // Translate and compile the app to DEX code as main-dex list requires DEX inputs.
+    D8TestBuilder builder =
+        testForD8(getStaticTemp(), Backend.DEX).setOutputMode(OutputMode.DexFilePerClass);
     for (String typename : classes) {
       String descriptor = DescriptorUtils.javaTypeToDescriptor(typename);
       byte[] bytes =
@@ -702,9 +700,13 @@
                     }
                   })
               .transform();
-      consumer.accept(ByteDataView.of(bytes), descriptor, null);
+      builder.addProgramClassFileData(bytes);
     }
-    consumer.finished(null);
+    try {
+      builder.compile().writeToZip(output);
+    } catch (CompilationFailedException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   // Simple stub/template for generating the input classes.
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
index a5a07c4..8ce0413 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
@@ -48,15 +48,6 @@
     testMainDex(builder -> {}, Assert::assertNull);
   }
 
-  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-  @Test
-  public void testMainDexClassesList() throws Exception {
-    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
-    testMainDex(
-        builder -> builder.addMainDexListClasses(Main.class).allowDiagnosticWarningMessages(),
-        mainDexClasses -> assertEquals(ImmutableSet.of(Main.class.getTypeName()), mainDexClasses));
-  }
-
   @Test
   public void testMainDexTracing() throws Exception {
     assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java
deleted file mode 100644
index ae91c3d..0000000
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.maindexlist;
-
-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.utils.FileUtils;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class MainDexRulesAndListD8 extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withApiLevelsWithoutNativeMultiDex().build();
-  }
-
-  public MainDexRulesAndListD8(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private static Path testDir;
-  private static Path mainDexRules;
-  private static Path mainDexList;
-
-  @BeforeClass
-  public static void setUp() throws Exception {
-    testDir = getStaticTemp().newFolder().toPath();
-    mainDexRules = testDir.resolve("main-dex-rules");
-    mainDexList = testDir.resolve("main-dex-list");
-    FileUtils.writeTextFile(mainDexRules, ImmutableList.of("-keep class " + A.class.getTypeName()));
-    FileUtils.writeTextFile(
-        mainDexList, ImmutableList.of(B.class.getTypeName().replace('.', '/') + ".class"));
-  }
-
-  @Test
-  public void test() throws Exception {
-    Path result =
-        testForD8(parameters.getBackend())
-            .setMinApi(parameters)
-            .addInnerClasses(getClass())
-            .addMainDexRulesFiles(mainDexRules)
-            .addMainDexListFiles(mainDexList)
-            .debug()
-            .compile()
-            .writeToZip();
-    List<Path> dexFiles =
-        ZipUtils.unzip(result, testDir).stream().sorted().collect(Collectors.toList());
-    assertEquals(
-        classNamesFromDexFile(dexFiles.get(0)).stream().sorted().collect(Collectors.toList()),
-        ImmutableList.of(A.class.getTypeName(), B.class.getTypeName()));
-    assertEquals(
-        classNamesFromDexFile(dexFiles.get(1)).stream().sorted().collect(Collectors.toList()),
-        ImmutableList.of(C.class.getTypeName()));
-  }
-
-  static class A {}
-
-  static class B {}
-
-  static class C {}
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index 1f980f3..2cc6ad2 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -6,12 +6,13 @@
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestDiagnosticMessages;
@@ -125,72 +126,35 @@
    * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
    * included in the main-dex file.
    *
-   * <p>TODO(b/181858113): Update to assert an error is raised once deprecated period is over.
+   * <p>Now that b/181858113 is resolved to disallow this API usage, this test just checks the error
+   * is reported.
    */
   @Test
   public void testDeprecatedSyntheticsFromMainDexListD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     Path mainDexFile = temp.newFile("maindex.list").toPath();
     FileUtils.writeTextFile(mainDexFile, binaryName(A.class) + ".class");
-    D8TestCompileResult compileResult =
-        testForD8()
-            .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
-            .addMainDexListClasses(TestClass.class)
-            .addMainDexListFiles(mainDexFile)
-            .setMinApi(parameters)
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics
-                        .assertOnlyWarnings()
-                        .assertWarningsMatch(
-                            // The "classes" addition has no origin.
-                            allOf(
-                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
-                                diagnosticOrigin(Origin.unknown())),
-                            // The "file" addition must have the file origin.
-                            allOf(
-                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
-                                diagnosticOrigin(new PathOrigin(mainDexFile)))));
-    checkCompilationResult(compileResult);
-  }
-
-  /**
-   * This test checks for maintained support of including synthetics from main-dex-list entries in
-   * the main-dex file. This test simulates that the tracing done at the class-file level has
-   * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
-   * included in the main-dex file.
-   *
-   * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed.
-   */
-  @Test
-  public void testDeprecatedSyntheticsFromMainDexListR8() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    Path mainDexFile = temp.newFile("maindex.list").toPath();
-    FileUtils.writeTextFile(mainDexFile, binaryName(A.class) + ".class");
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
-            .setMinApi(parameters)
-            .addOptionsModification(o -> o.minimalMainDex = true)
-            .addMainDexListClasses(TestClass.class)
-            .addMainDexListFiles(mainDexFile)
-            .addDontObfuscate()
-            .noTreeShaking()
-            .allowDiagnosticWarningMessages()
-            .compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics
-                        .assertOnlyWarnings()
-                        .assertWarningsMatch(
-                            // The "classes" addition has no origin.
-                            allOf(
-                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
-                                diagnosticOrigin(Origin.unknown())),
-                            // The "file" addition must have the file origin.
-                            allOf(
-                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
-                                diagnosticOrigin(new PathOrigin(mainDexFile)))));
-    checkCompilationResult(compileResult, compileResult.app);
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8()
+                .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+                .addMainDexListClasses(TestClass.class)
+                .addMainDexListFiles(mainDexFile)
+                .setMinApi(parameters)
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics
+                            .assertOnlyErrors()
+                            .assertErrorsMatch(
+                                // The "classes" addition has no origin.
+                                allOf(
+                                    diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+                                    diagnosticOrigin(Origin.unknown())),
+                                // The "file" addition must have the file origin.
+                                allOf(
+                                    diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+                                    diagnosticOrigin(new PathOrigin(mainDexFile))))));
   }
 
   private void checkCompilationResult(D8TestCompileResult compileResult) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
index cb7e981..ecd91e7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -4,13 +4,19 @@
 
 package com.android.tools.r8.maindexlist.warnings;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -61,7 +67,7 @@
         .assertNoMessages();
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testWarningFromManualMainDexList() throws Exception {
     testForR8(parameters.getBackend())
         .setMinApi(AndroidApiLevel.K)
@@ -72,15 +78,21 @@
         .addMainDexListClasses(Static.class)
         .allowDiagnosticWarningMessages()
         .setMinApi(parameters)
-        .compile()
-        .inspect(this::classStaticGone)
-        .assertOnlyWarnings()
-        .assertWarningMessageThatMatches(containsString("Application does not contain"))
-        .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
-        .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"));
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics
+                  .assertNoInfos()
+                  .assertErrorsMatch(diagnosticType(UnsupportedMainDexListUsageDiagnostic.class))
+                  .assertWarningsMatch(
+                      allOf(
+                          diagnosticMessage(containsString("Application does not contain")),
+                          diagnosticMessage(containsString(Static.class.getTypeName())),
+                          diagnosticMessage(containsString("as referenced in main-dex-list")),
+                          not(diagnosticMessage(containsString(Static2.class.getTypeName())))));
+            });
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testWarningFromManualMainDexListWithRuleAsWell() throws Exception {
     testForR8(parameters.getBackend())
         .setMinApi(AndroidApiLevel.K)
@@ -93,13 +105,18 @@
         .addDontWarn(Static.class)
         .allowDiagnosticWarningMessages()
         .setMinApi(parameters)
-        .compile()
-        .inspect(this::classStaticGone)
-        .assertOnlyWarnings()
-        .assertWarningMessageThatMatches(containsString("Application does not contain"))
-        .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
-        .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"))
-        .assertNoWarningMessageThatMatches(containsString(Static2.class.getTypeName()));
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics
+                  .assertNoInfos()
+                  .assertErrorsMatch(diagnosticType(UnsupportedMainDexListUsageDiagnostic.class))
+                  .assertWarningsMatch(
+                      allOf(
+                          diagnosticMessage(containsString("Application does not contain")),
+                          diagnosticMessage(containsString(Static.class.getTypeName())),
+                          diagnosticMessage(containsString("as referenced in main-dex-list")),
+                          not(diagnosticMessage(containsString(Static2.class.getTypeName())))));
+            });
   }
 }
 
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
index 584814b..acd0d3f 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
@@ -49,8 +49,8 @@
         : parameters.getRuntime().maxSupportedApiLevel();
   }
 
-  public boolean isLibraryClassAlwaysPresent(boolean isDesugaring) {
-    return !isDesugaring || parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
+  public boolean isLibraryClassAlwaysPresent(boolean isApiOutlining) {
+    return !isApiOutlining || parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
   }
 
   public boolean isLibraryClassPresentInCurrentRuntime() {
@@ -114,7 +114,7 @@
 
   private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector)
       throws Exception {
-    inspect(profileInspector, inspector, isLibraryClassAlwaysPresent(true));
+    inspect(profileInspector, inspector, isLibraryClassAlwaysPresent(parameters.isDexRuntime()));
   }
 
   private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector)
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
index 8554ae3..b8db6f5 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/TwrCloseResourceDuplicationProfileRewritingTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.desugar.LibraryFilesHelper.getJdk11LibraryFiles;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -111,11 +112,6 @@
         .build();
   }
 
-  private boolean hasTwrCloseResourceSupport(boolean isDesugaring) {
-    return !isDesugaring
-        || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport());
-  }
-
   private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) {
     inspect(profileInspector, inspector, hasTwrCloseResourceSupport(true));
   }
@@ -128,10 +124,17 @@
       ArtProfileInspector profileInspector,
       CodeInspector inspector,
       boolean hasTwrCloseResourceSupport) {
+    int expectedClassCount = 3;
+    if (!hasTwrCloseResourceSupport) {
+      expectedClassCount += 8;
+    }
+    if (hasTwrCloseResourceApiOutlines()) {
+      expectedClassCount += 4;
+    }
     inspector
         .allClasses()
         .forEach(c -> System.out.println(c.getDexProgramClass().toSourceString()));
-    assertEquals(hasTwrCloseResourceSupport ? 3 : 15, inspector.allClasses().size());
+    assertEquals(expectedClassCount, inspector.allClasses().size());
     assertThat(inspector.clazz(MAIN.typeName()), isPresent());
 
     // Class Foo has two methods foo() and $closeResource().
@@ -167,49 +170,60 @@
       ClassSubject syntheticApiOutlineClassSubject0 =
           inspector.clazz(
               SyntheticItemsTestUtils.syntheticApiOutlineClass(clazz.getClassReference(), 0));
-      assertThat(syntheticApiOutlineClassSubject0, notIf(isPresent(), hasTwrCloseResourceSupport));
+      assertThat(syntheticApiOutlineClassSubject0, isPresentIf(hasTwrCloseResourceApiOutlines()));
 
       ClassSubject syntheticApiOutlineClassSubject1 =
           inspector.clazz(
               SyntheticItemsTestUtils.syntheticApiOutlineClass(clazz.getClassReference(), 1));
-      assertThat(syntheticApiOutlineClassSubject1, notIf(isPresent(), hasTwrCloseResourceSupport));
+      assertThat(syntheticApiOutlineClassSubject1, isPresentIf(hasTwrCloseResourceApiOutlines()));
+
+      int initialSyntheticId = hasTwrCloseResourceApiOutlines() ? 2 : 0;
 
       ClassSubject syntheticBackportClassSubject =
           inspector.clazz(
-              SyntheticItemsTestUtils.syntheticBackportClass(clazz.getClassReference(), 2));
+              SyntheticItemsTestUtils.syntheticBackportClass(
+                  clazz.getClassReference(), initialSyntheticId));
       assertThat(syntheticBackportClassSubject, notIf(isPresent(), hasTwrCloseResourceSupport));
 
       ClassSubject syntheticTwrCloseResourceClassSubject3 =
           inspector.clazz(
-              SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(clazz.getClassReference(), 3));
+              SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(
+                  clazz.getClassReference(), initialSyntheticId + 1));
       assertThat(
           syntheticTwrCloseResourceClassSubject3, notIf(isPresent(), hasTwrCloseResourceSupport));
 
       ClassSubject syntheticTwrCloseResourceClassSubject4 =
           inspector.clazz(
-              SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(clazz.getClassReference(), 4));
+              SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(
+                  clazz.getClassReference(), initialSyntheticId + 2));
       assertThat(
           syntheticTwrCloseResourceClassSubject4, notIf(isPresent(), hasTwrCloseResourceSupport));
 
       ClassSubject syntheticTwrCloseResourceClassSubject5 =
           inspector.clazz(
-              SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(clazz.getClassReference(), 5));
+              SyntheticItemsTestUtils.syntheticTwrCloseResourceClass(
+                  clazz.getClassReference(), initialSyntheticId + 3));
       assertThat(
           syntheticTwrCloseResourceClassSubject5, notIf(isPresent(), hasTwrCloseResourceSupport));
 
       profileInspector.applyIf(
+          hasTwrCloseResourceApiOutlines(),
+          i ->
+              i.assertContainsClassRules(
+                      syntheticApiOutlineClassSubject0, syntheticApiOutlineClassSubject1)
+                  .assertContainsMethodRules(
+                      syntheticApiOutlineClassSubject0.uniqueMethod(),
+                      syntheticApiOutlineClassSubject1.uniqueMethod()));
+
+      profileInspector.applyIf(
           !hasTwrCloseResourceSupport,
           i ->
               i.assertContainsClassRules(
-                      syntheticApiOutlineClassSubject0,
-                      syntheticApiOutlineClassSubject1,
                       syntheticBackportClassSubject,
                       syntheticTwrCloseResourceClassSubject3,
                       syntheticTwrCloseResourceClassSubject4,
                       syntheticTwrCloseResourceClassSubject5)
                   .assertContainsMethodRules(
-                      syntheticApiOutlineClassSubject0.uniqueMethod(),
-                      syntheticApiOutlineClassSubject1.uniqueMethod(),
                       syntheticBackportClassSubject.uniqueMethod(),
                       syntheticTwrCloseResourceClassSubject3.uniqueMethod(),
                       syntheticTwrCloseResourceClassSubject4.uniqueMethod(),
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
deleted file mode 100644
index 170708d..0000000
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.repackage;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-// TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
-@RunWith(Parameterized.class)
-public class RepackageWithMainDexListTest extends RepackageTestBase {
-
-  @Parameters(name = "{1}, kind: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
-        getTestParameters()
-            .withDexRuntimes()
-            .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-            .build());
-  }
-
-  public RepackageWithMainDexListTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    super(flattenPackageHierarchyOrRepackageClasses, parameters);
-  }
-
-  @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        // -keep,allowobfuscation does not prohibit repackaging.
-        .addKeepClassRulesWithAllowObfuscation(TestClass.class, OtherTestClass.class)
-        .addKeepRules(
-            "-keepclassmembers class " + TestClass.class.getTypeName() + " { <methods>; }")
-        // Add a class that will be repackaged to the main dex list.
-        .addMainDexListClasses(TestClass.class)
-        .apply(this::configureRepackaging)
-        // Debug mode to enable minimal main dex.
-        .debug()
-        .setMinApi(parameters)
-        .allowDiagnosticMessages()
-        .compileWithExpectedDiagnostics(
-            diagnostics ->
-                diagnostics
-                    .assertOnlyWarnings()
-                    .assertWarningsMatch(
-                        diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)))
-        .apply(this::checkCompileResult)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
-  }
-
-  private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
-    Path out = temp.newFolder().toPath();
-    compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
-    Path classes = out.resolve("classes.dex");
-    Path classes2 = out.resolve("classes2.dex");
-    inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
-    inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
-  }
-
-  private void inspectMainDex(CodeInspector inspector) {
-    assertThat(inspector.clazz(TestClass.class), isPresent());
-    assertThat(inspector.clazz(OtherTestClass.class), not(isPresent()));
-  }
-
-  private void inspectSecondaryDex(CodeInspector inspector) {
-    assertThat(inspector.clazz(TestClass.class), not(isPresent()));
-    assertThat(inspector.clazz(OtherTestClass.class), isPresent());
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      System.out.println("Hello world!");
-    }
-  }
-
-  static class OtherTestClass {}
-}
diff --git a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
index 0423904..da2771f 100644
--- a/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/JvmTestBuilder.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.debug.CfDebugTestConfig;
-import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -199,7 +198,7 @@
   }
 
   public JvmTestBuilder addAndroidBuildVersion() {
-    return addAndroidBuildVersion(AndroidApiLevel.MASTER);
+    return addAndroidBuildVersion(AndroidApiLevel.MAIN);
   }
 
   public JvmTestBuilder addAndroidBuildVersion(AndroidApiLevel apiLevel) {
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
index 3993296..4caf75f 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -130,6 +130,10 @@
 
   public T addAndroidBuildVersion(AndroidApiLevel specifiedApiLevel) {
     addProgramClasses(AndroidBuildVersion.class);
+    return markAndroidBuildVersionAsActive(specifiedApiLevel);
+  }
+
+  public T markAndroidBuildVersionAsActive(AndroidApiLevel specifiedApiLevel) {
     isAndroidBuildVersionAdded =
         Optional.ofNullable(specifiedApiLevel == null ? null : specifiedApiLevel.getLevel());
     return self();
@@ -619,7 +623,10 @@
           GlobalSyntheticsGeneratorCommand.builder()
               .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
               .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setGlobalSyntheticsConsumer(
+                  (data, context, handler) -> {
+                    // Ignore the data and context, callback is hit below.
+                  })
               .build();
       InternalOptions internalOptions = command.getInternalOptions();
       internalOptions.testing.globalSyntheticCreatedCallback =
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index d3481a6..9eea7dd 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -211,9 +211,6 @@
   public static final String ASM_JAR = BUILD_DIR + "deps/asm-9.6.jar";
   public static final String ASM_UTIL_JAR = BUILD_DIR + "deps/asm-util-9.6.jar";
 
-  public static final Path API_SAMPLE_JAR =
-      Paths.get(getProjectRoot(), "tests", "r8_api_usage_sample.jar");
-
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
   public static final String CLASSPATH_SEPARATOR = File.pathSeparator;
 
@@ -1230,8 +1227,8 @@
   }
 
   private static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
-    if (apiLevel == AndroidApiLevel.MASTER) {
-      return Paths.get(THIRD_PARTY_DIR + "android_jar/lib-master/android.jar");
+    if (apiLevel == AndroidApiLevel.MAIN) {
+      return Paths.get(THIRD_PARTY_DIR + "android_jar/lib-main/android.jar");
     }
     String jar = String.format(
         ANDROID_JAR_PATTERN,
@@ -1401,7 +1398,7 @@
   public static AndroidApiLevel getMinApiLevelForDexVm(DexVm dexVm) {
     switch (dexVm.version) {
       case MASTER:
-        return AndroidApiLevel.MASTER;
+        return AndroidApiLevel.MAIN;
       case V14_0_0:
         return AndroidApiLevel.U;
       case V13_0_0:
@@ -1433,7 +1430,7 @@
 
   public static DexVm.Version getDexVersionForApiLevel(AndroidApiLevel apiLevel) {
     switch (apiLevel) {
-      case MASTER:
+      case MAIN:
         return DexVm.Version.MASTER;
       case U:
         return DexVm.Version.V14_0_0;
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
deleted file mode 100644
index 61fd5f8..0000000
--- a/tests/r8_api_usage_sample.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/android_jar/lib-main.tar.gz.sha1 b/third_party/android_jar/lib-main.tar.gz.sha1
new file mode 100644
index 0000000..7ef1faa
--- /dev/null
+++ b/third_party/android_jar/lib-main.tar.gz.sha1
@@ -0,0 +1 @@
+9cff91a12817179c3058b79e22934b3ef71971fc
\ No newline at end of file
diff --git a/third_party/android_jar/lib-master.tar.gz.sha1 b/third_party/android_jar/lib-master.tar.gz.sha1
deleted file mode 100644
index 717d805..0000000
--- a/third_party/android_jar/lib-master.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a759a6727431a73b4f461ed40aa5bfbc79ebc172
\ No newline at end of file
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 009f4e4..32f7368 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-9de592304071fa794e07e9e8d955aa4ae62cdef1
\ No newline at end of file
+34436ff2aee451c7d8e5c0f28193134822238d4b
\ No newline at end of file
diff --git a/third_party/gmail/gmail_android_180826.15.tar.gz.sha1 b/third_party/gmail/gmail_android_180826.15.tar.gz.sha1
deleted file mode 100644
index 3789746..0000000
--- a/third_party/gmail/gmail_android_180826.15.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f3770f048210068c1920b10ab5d09b19da3a8ea7
\ No newline at end of file
diff --git a/tools/compare_apk_sizes.py b/tools/compare_apk_sizes.py
index 6349689..5ed768b 100755
--- a/tools/compare_apk_sizes.py
+++ b/tools/compare_apk_sizes.py
@@ -112,7 +112,7 @@
                     entry.set_size(False)
                 file_info_map[file_path] = entry
     threads = []
-    file_infos = file_info_map.values() if options.use_code_size else []
+    file_infos = list(file_info_map.values()) if options.use_code_size else []
     while len(file_infos) > 0 or len(threads) > 0:
         for t in threads:
             if not t.is_alive():
diff --git a/tools/d8.py b/tools/d8.py
index aab63ed..636b7e7 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -23,6 +23,11 @@
                       help='Print the line \'<BENCHMARKNAME>(RunTimeRaw):' +
                       ' <elapsed> ms\' at the end where <elapsed> is' +
                       ' the elapsed time in milliseconds.')
+    parser.add_option('--no-build',
+                      '--no_build',
+                      help='Do not build D8',
+                      default=False,
+                      action='store_true')
     parser.add_option('--version', help='Version of D8 to use.', default=None)
     parser.add_option('--tag', help='Tag of D8 to use.', default=None)
     return parser.parse_args(argv)
@@ -34,6 +39,7 @@
     time_consumer = lambda duration: print_duration(duration, options)
     return toolhelper.run('d8',
                           d8_args,
+                          build=not options.no_build,
                           jar=utils.find_r8_jar_from_options(options),
                           main='com.android.tools.r8.D8',
                           time_consumer=time_consumer)
diff --git a/tools/gmail_data.py b/tools/gmail_data.py
deleted file mode 100644
index 6170a66..0000000
--- a/tools/gmail_data.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-import glob
-import os
-import utils
-
-ANDROID_L_API = '21'
-BASE = os.path.join(utils.THIRD_PARTY, 'gmail')
-
-V180826_15_BASE = os.path.join(BASE, 'gmail_android_180826.15')
-V180826_15_PREFIX = os.path.join(V180826_15_BASE, 'Gmail_release_unstripped')
-
-# NOTE: We always use android.jar for SDK v25 for now.
-ANDROID_JAR = utils.get_android_jar(25)
-
-VERSIONS = {
-    '180826.15': {
-        'dex': {
-            'flags': '--no-desugaring',
-            'inputs': [
-                os.path.join(V180826_15_BASE, 'Gmail_release_unsigned.apk')
-            ],
-            'main-dex-list': os.path.join(V180826_15_BASE, 'main_dex_list.txt'),
-            'pgmap': '%s_proguard.map' % V180826_15_PREFIX,
-            'libraries': [ANDROID_JAR],
-        },
-        'deploy': {
-            'flags': '--no-desugaring',
-            'inputs': ['%s_deploy.jar' % V180826_15_PREFIX],
-            'pgconf': [
-                '%s_proguard.config' % V180826_15_PREFIX,
-                '%s/proguardsettings/Gmail_proguard.config' % utils.THIRD_PARTY,
-                utils.IGNORE_WARNINGS_RULES
-            ],
-            'min-api': ANDROID_L_API,
-            'allow-type-errors': 1,
-        },
-        'proguarded': {
-            'flags': '--no-desugaring',
-            'inputs': ['%s_proguard.jar' % V180826_15_PREFIX],
-            'main-dex-list': os.path.join(V180826_15_BASE, 'main_dex_list.txt'),
-            'pgmap': '%s_proguard.map' % V180826_15_PREFIX,
-        }
-    },
-}
diff --git a/tools/historic_memory_usage.py b/tools/historic_memory_usage.py
index a40cf2a..2256e28 100755
--- a/tools/historic_memory_usage.py
+++ b/tools/historic_memory_usage.py
@@ -17,7 +17,7 @@
 import sys
 import utils
 
-APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome']
+APPS = ['gmscore', 'nest', 'youtube', 'chrome']
 COMPILERS = ['d8', 'r8']
 
 
@@ -29,7 +29,6 @@
                       choices=COMPILERS)
     result.add_option('--app',
                       help='What app to run on',
-                      default='gmail',
                       choices=APPS)
     result.add_option('--top',
                       default=historic_run.top_or_default(),
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 3aaa40d..c3f8307 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -14,7 +14,6 @@
 
 import archive
 import gradle
-import gmail_data
 import nest_data
 from sanitize_libraries import SanitizeLibraries, SanitizeLibrariesInPgconf
 import thread_utils
@@ -26,7 +25,7 @@
 import chrome_data
 
 TYPES = ['dex', 'deploy', 'proguarded']
-APPS = ['nest', 'youtube', 'gmail', 'chrome']
+APPS = ['nest', 'youtube', 'chrome']
 COMPILERS = ['d8', 'r8']
 COMPILER_BUILDS = ['full', 'lib']
 
@@ -230,7 +229,6 @@
         'nest': nest_data,
         'youtube': youtube_data,
         'chrome': chrome_data,
-        'gmail': gmail_data,
     }
     # Check to ensure that we add all variants here.
     assert len(APPS) == len(data_providers)
@@ -431,9 +429,6 @@
     elif options.app == 'chrome':
         version = options.version or '180917'
         data = chrome_data
-    elif options.app == 'gmail':
-        version = options.version or '170604.16'
-        data = gmail_data
     else:
         raise Exception("You need to specify '--app={}'".format('|'.join(APPS)))
     return version, data