Merge commit 'cfe1b2aae0b66991cdf07cdd059d350b60799ed8' into dev-release
diff --git a/.gitignore b/.gitignore
index 6d67701..13e30c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@
 android-data*/
 build/
 buildSrc/out/
+src/*/out
 gradle-app.setting
 gradle/*
 gradlew
@@ -25,6 +26,7 @@
 r8.ipr
 r8.iws
 src/test/jack/ub-jack
+test_modules/tests_java_8/libs/
 tests/2016-12-19/art
 tests/2016-12-19/art.tar.gz
 tests/2017-10-04/art
@@ -99,6 +101,8 @@
 third_party/gradle-plugin.tar.gz
 third_party/gradle/gradle
 third_party/gradle/gradle.tar.gz
+third_party/gradle/gradle-8.0
+third_party/gradle/gradle-8.0.tar.gz
 third_party/internal/*
 third_party/iosched_2019
 third_party/iosched_2019.tar.gz
@@ -282,6 +286,8 @@
 tools/*/host/art-12.0.0-beta4.tar.gz
 tools/*/host/art-13.0.0
 tools/*/host/art-13.0.0.tar.gz
+tools/*/host/art-14.0.0-dp1
+tools/*/host/art-14.0.0-dp1.tar.gz
 tools/*/host/art-master
 tools/*/host/art-master.tar.gz
 tools/*/art.tar.gz
diff --git a/build.gradle b/build.gradle
index 759507d..11cc609 100644
--- a/build.gradle
+++ b/build.gradle
@@ -393,6 +393,7 @@
                 "linux/art-10.0.0",
                 "linux/host/art-12.0.0-beta4",
                 "linux/host/art-13.0.0",
+                "linux/host/art-14.0.0-dp1",
                 "linux/host/art-master",
                 "linux/dalvik",
                 "linux/dalvik-4.0.4",
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 3f4e21f..88e29ab 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -7,18 +7,19 @@
 repositories {
     mavenCentral()
 }
+
 ext {
     asmVersion = '9.4'
 }
 
 dependencies {
-    compile group: 'com.google.guava', name: 'guava', version: '19.0'
-    compile group: 'org.smali', name: 'smali', version: '2.2b4'
-    compile group: 'org.ow2.asm', name: 'asm', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
+    implementation group: 'com.google.guava', name: 'guava', version: '30.1.1-jre'
+    implementation group: 'org.smali', name: 'smali', version: '2.2b4'
+    implementation group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
 }
 
 sourceCompatibility = JavaVersion.VERSION_1_8
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriterTask.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriterTask.java
index bae3c1c..1a09676 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriterTask.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriterTask.java
@@ -9,10 +9,12 @@
 import java.io.UncheckedIOException;
 import javax.inject.Inject;
 import org.gradle.api.DefaultTask;
+import org.gradle.api.file.RegularFileProperty;
 import org.gradle.api.tasks.InputFile;
 import org.gradle.api.tasks.OutputDirectory;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkAction;
+import org.gradle.workers.WorkParameters;
 import org.gradle.workers.WorkerExecutor;
 
 public class CustomConversionAsmRewriterTask extends DefaultTask {
@@ -47,29 +49,31 @@
 
   @TaskAction
   void exec() {
-    workerExecutor.submit(
-        Run.class,
-        config -> {
-          config.setIsolationMode(IsolationMode.NONE);
-          config.params(rawJar, outputDirectory);
-        });
+    workerExecutor
+        .noIsolation()
+        .submit(
+            Run.class,
+            parameters -> {
+              parameters.getRawJar().set(rawJar);
+              parameters.getOutputDirectory().set(outputDirectory);
+            });
   }
 
-  public static class Run implements Runnable {
+  public interface RunParameters extends WorkParameters {
+    RegularFileProperty getRawJar();
 
-    private final File rawJar;
-    private final File outputDirectory;
+    RegularFileProperty getOutputDirectory();
+  }
 
-    @Inject
-    public Run(File rawJar, File outputDirectory) {
-      this.rawJar = rawJar;
-      this.outputDirectory = outputDirectory;
-    }
+  public abstract static class Run implements WorkAction<RunParameters> {
 
     @Override
-    public void run() {
+    public void execute() {
       try {
-        CustomConversionAsmRewriter.generateJars(rawJar.toPath(), outputDirectory.toPath());
+        RunParameters parameters = getParameters();
+        CustomConversionAsmRewriter.generateJars(
+            parameters.getRawJar().getAsFile().get().toPath(),
+            parameters.getOutputDirectory().getAsFile().get().toPath());
       } catch (IOException e) {
         throw new UncheckedIOException(e);
       }
diff --git a/buildSrc/src/main/java/dx/DexMergerTask.java b/buildSrc/src/main/java/dx/DexMergerTask.java
index 5dea52b..409337c 100644
--- a/buildSrc/src/main/java/dx/DexMergerTask.java
+++ b/buildSrc/src/main/java/dx/DexMergerTask.java
@@ -3,20 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package dx;
 
-import com.google.common.base.Optional;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 import javax.inject.Inject;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.tasks.InputFile;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.SetProperty;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkAction;
+import org.gradle.workers.WorkParameters;
 import org.gradle.workers.WorkerExecutor;
 import utils.Utils;
 
@@ -26,7 +26,6 @@
 
   private FileCollection source;
   private File destination;
-  private Optional<File> dexMergerExecutable = Optional.absent(); // Worker API cannot handle null.
 
   @Inject
   public DexMergerTask(WorkerExecutor workerExecutor) {
@@ -51,57 +50,39 @@
     this.destination = destination;
   }
 
-  @InputFile
-  @org.gradle.api.tasks.Optional
-  public File getDexMergerExecutable() {
-    return dexMergerExecutable.orNull();
-  }
-
-  public void setDexMergerExecutable(File dexMergerExecutable) {
-    this.dexMergerExecutable = Optional.fromNullable(dexMergerExecutable);
-  }
-
   @TaskAction
   void exec() {
-    workerExecutor.submit(
-        RunDexMerger.class,
-        config -> {
-          File executable =
-              dexMergerExecutable.isPresent()
-                  ? dexMergerExecutable.get()
-                  : config
-                      .getForkOptions()
-                      .getWorkingDir()
-                      .toPath()
-                      .resolve(Utils.dexMergerExecutable())
-                      .toFile();
-          config.setIsolationMode(IsolationMode.NONE);
-          config.params(source.getFiles(), destination, executable);
-        });
+    workerExecutor
+        .noIsolation()
+        .submit(
+            RunDexMerger.class,
+            parameters -> {
+              parameters.getSources().set(source.getFiles());
+              parameters.getDestination().set(destination);
+            });
   }
 
-  public static class RunDexMerger implements Runnable {
-    private final Set<File> sources;
-    private final File destination;
-    private final File dexMergerExecutable;
+  public interface RunDexMergerParameters extends WorkParameters {
 
-    @Inject
-    public RunDexMerger(Set<File> sources, File destination, File dexMergerExecutable) {
-      this.sources = sources;
-      this.destination = destination;
-      this.dexMergerExecutable = dexMergerExecutable;
-    }
+    SetProperty<File> getSources();
+
+    RegularFileProperty getDestination();
+
+    RegularFileProperty getDexMergerExecutable();
+  }
+
+  public abstract static class RunDexMerger implements WorkAction<RunDexMergerParameters> {
 
     @Override
-    public void run() {
+    public void execute() {
       try {
+        RunDexMergerParameters parameters = getParameters();
         List<String> command = new ArrayList<>();
-        command.add(dexMergerExecutable.getCanonicalPath());
-        command.add(destination.getCanonicalPath());
-        for (File source : sources) {
+        command.add(Utils.dexMergerExecutable().toString());
+        command.add(parameters.getDestination().getAsFile().get().getCanonicalPath());
+        for (File source : parameters.getSources().get()) {
           command.add(source.getCanonicalPath());
         }
-
         Process dexMerger = new ProcessBuilder(command).inheritIO().start();
         int exitCode = dexMerger.waitFor();
         if (exitCode != 0) {
diff --git a/buildSrc/src/main/java/dx/DxTask.java b/buildSrc/src/main/java/dx/DxTask.java
index e05e8fb..a66733a 100644
--- a/buildSrc/src/main/java/dx/DxTask.java
+++ b/buildSrc/src/main/java/dx/DxTask.java
@@ -3,22 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package dx;
 
-import com.google.common.base.Optional;
 import java.io.File;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 import javax.inject.Inject;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.provider.SetProperty;
 import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputDirectory;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkAction;
+import org.gradle.workers.WorkParameters;
 import org.gradle.workers.WorkerExecutor;
 import utils.Utils;
 
@@ -28,7 +29,6 @@
 
   private FileCollection source;
   private File destination;
-  private Optional<File> dxExecutable = Optional.absent(); // Worker API cannot handle null.
   private boolean debug;
 
   @Inject
@@ -54,16 +54,6 @@
     this.destination = destination;
   }
 
-  @InputFile
-  @org.gradle.api.tasks.Optional
-  public File getDxExecutable() {
-    return dxExecutable.orNull();
-  }
-
-  public void setDxExecutable(File dxExecutable) {
-    this.dxExecutable = Optional.fromNullable(dxExecutable);
-  }
-
   @Input
   public boolean isDebug() {
     return debug;
@@ -75,49 +65,41 @@
 
   @TaskAction
   void exec() {
-    workerExecutor.submit(
-        RunDx.class,
-        config -> {
-          File executable =
-              dxExecutable.isPresent()
-                  ? dxExecutable.get()
-                  : config
-                      .getForkOptions()
-                      .getWorkingDir()
-                      .toPath()
-                      .resolve(Utils.dxExecutable())
-                      .toFile();
-          config.setIsolationMode(IsolationMode.NONE);
-          config.params(source.getFiles(), destination, executable, debug);
-        });
+    workerExecutor
+        .noIsolation()
+        .submit(
+            RunDx.class,
+            parameters -> {
+              parameters.getSources().set(source.getFiles());
+              parameters.getDestination().set(destination);
+              parameters.getDebug().set(debug);
+            });
   }
 
-  public static class RunDx implements Runnable {
-    private final Set<File> sources;
-    private final File destination;
-    private final File dxExecutable;
-    private final boolean debug;
+  public interface RunDxParameters extends WorkParameters {
 
-    @Inject
-    public RunDx(Set<File> sources, File destination, File dxExecutable, boolean debug) {
-      this.sources = sources;
-      this.destination = destination;
-      this.dxExecutable = dxExecutable;
-      this.debug = debug;
-    }
+    SetProperty<File> getSources();
+
+    RegularFileProperty getDestination();
+
+    Property<Boolean> getDebug();
+  }
+
+  public abstract static class RunDx implements WorkAction<RunDxParameters> {
 
     @Override
-    public void run() {
+    public void execute() {
+      RunDxParameters parameters = getParameters();
       try {
         List<String> command = new ArrayList<>();
-        command.add(dxExecutable.getCanonicalPath());
+        command.add(Utils.dxExecutable().toString());
         command.add("--dex");
         command.add("--output");
-        command.add(destination.getCanonicalPath());
-        if (debug) {
+        command.add(parameters.getDestination().getAsFile().get().getCanonicalPath());
+        if (parameters.getDebug().get()) {
           command.add("--debug");
         }
-        for (File source : sources) {
+        for (File source : parameters.getSources().get()) {
           command.add(source.getCanonicalPath());
         }
 
diff --git a/buildSrc/src/main/java/smali/SmaliTask.java b/buildSrc/src/main/java/smali/SmaliTask.java
index 845592e..dd4e10c 100644
--- a/buildSrc/src/main/java/smali/SmaliTask.java
+++ b/buildSrc/src/main/java/smali/SmaliTask.java
@@ -9,14 +9,16 @@
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.List;
-import java.util.Set;
 import javax.inject.Inject;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.SetProperty;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkAction;
+import org.gradle.workers.WorkParameters;
 import org.gradle.workers.WorkerExecutor;
 import org.jf.smali.Smali;
 import org.jf.smali.SmaliOptions;
@@ -53,28 +55,33 @@
 
   @TaskAction
   void exec() {
-    workerExecutor.submit(RunSmali.class, config -> {
-      config.setIsolationMode(IsolationMode.NONE);
-      config.params(source.getFiles(), destination);
-    });
+    workerExecutor
+        .noIsolation()
+        .submit(
+            RunSmali.class,
+            parameters -> {
+              parameters.getSources().set(source.getFiles());
+              parameters.getDestination().set(destination);
+            });
   }
 
-  public static class RunSmali implements Runnable {
-    private final Set<File> sources;
-    private final File destination;
+  public interface RunSmaliParameters extends WorkParameters {
 
-    @Inject
-    public RunSmali(Set<File> sources, File destination) {
-      this.sources = sources;
-      this.destination = destination;
-    }
+    SetProperty<File> getSources();
+
+    RegularFileProperty getDestination();
+  }
+
+  public abstract static class RunSmali implements WorkAction<RunSmaliParameters> {
 
     @Override
-    public void run() {
+    public void execute() {
       try {
-        List<String> fileNames = sources.stream().map(File::toString).collect(toList());
+        RunSmaliParameters parameters = getParameters();
+        List<String> fileNames =
+            parameters.getSources().get().stream().map(File::toString).collect(toList());
         SmaliOptions options = new SmaliOptions();
-        options.outputDexFile = destination.getCanonicalPath();
+        options.outputDexFile = parameters.getDestination().getAsFile().get().getCanonicalPath();
         Smali.assemble(options, fileNames);
       } catch (IOException e) {
         throw new UncheckedIOException(e);
diff --git a/buildSrc/src/main/java/tasks/DownloadDependency.java b/buildSrc/src/main/java/tasks/DownloadDependency.java
index 59596a8..6d7a75c 100644
--- a/buildSrc/src/main/java/tasks/DownloadDependency.java
+++ b/buildSrc/src/main/java/tasks/DownloadDependency.java
@@ -15,11 +15,14 @@
 import java.util.stream.Collectors;
 import javax.inject.Inject;
 import org.gradle.api.DefaultTask;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Property;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputDirectory;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.internal.os.OperatingSystem;
-import org.gradle.workers.IsolationMode;
+import org.gradle.workers.WorkAction;
+import org.gradle.workers.WorkParameters;
 import org.gradle.workers.WorkerExecutor;
 
 public class DownloadDependency extends DefaultTask {
@@ -88,29 +91,34 @@
     if (outputDir.exists() && outputDir.isDirectory()) {
       outputDir.delete();
     }
-    workerExecutor.submit(RunDownload.class, config -> {
-      config.setIsolationMode(IsolationMode.NONE);
-      config.params(type, sha1File);
-    });
+    workerExecutor
+        .noIsolation()
+        .submit(
+            RunDownload.class,
+            parameters -> {
+              parameters.getType().set(type);
+              parameters.getSha1File().set(sha1File);
+            });
   }
 
-  public static class RunDownload implements Runnable {
-    private final Type type;
-    private final File sha1File;
+  public interface RunDownloadParameters extends WorkParameters {
+    Property<Type> getType();
 
-    @Inject
-    public RunDownload(Type type, File sha1File) {
-      this.type = type;
-      this.sha1File = sha1File;
-    }
+    RegularFileProperty getSha1File();
+  }
+
+  public abstract static class RunDownload implements WorkAction<RunDownloadParameters> {
 
     @Override
-    public void run() {
+    public void execute() {
       try {
+        RunDownloadParameters parameters = getParameters();
+        Type type = parameters.getType().get();
+        File sha1File = parameters.getSha1File().getAsFile().get();
         if (type == Type.GOOGLE_STORAGE) {
-          downloadFromGoogleStorage();
+          downloadFromGoogleStorage(sha1File);
         } else if (type == Type.X20) {
-          downloadFromX20();
+          downloadFromX20(sha1File);
         } else {
           throw new RuntimeException("Unexpected or missing dependency type: " + type);
         }
@@ -119,7 +127,7 @@
       }
     }
 
-    private void downloadFromGoogleStorage() throws IOException, InterruptedException {
+    private void downloadFromGoogleStorage(File sha1File) throws IOException, InterruptedException {
       List<String> args = Arrays.asList("-n", "-b", "r8-deps", "-s", "-u", sha1File.toString());
       if (OperatingSystem.current().isWindows()) {
         List<String> command = new ArrayList<>();
@@ -133,7 +141,7 @@
       }
     }
 
-    private void downloadFromX20() throws IOException, InterruptedException {
+    private void downloadFromX20(File sha1File) throws IOException, InterruptedException {
       if (OperatingSystem.current().isWindows()) {
         throw new RuntimeException("Downloading from x20 unsupported on windows");
       }
diff --git a/commonBuildSrc/build.gradle.kts b/commonBuildSrc/build.gradle.kts
new file mode 100644
index 0000000..ba393c7
--- /dev/null
+++ b/commonBuildSrc/build.gradle.kts
@@ -0,0 +1,15 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+plugins {
+  `kotlin-dsl`
+  `java-gradle-plugin`
+}
+
+gradlePlugin {
+  plugins.register("dependencies-plugin") {
+    id = "dependencies-plugin"
+    implementationClass = "DependenciesPlugin"
+  }
+}
diff --git a/commonBuildSrc/settings.gradle.kts b/commonBuildSrc/settings.gradle.kts
new file mode 100644
index 0000000..b7d4523
--- /dev/null
+++ b/commonBuildSrc/settings.gradle.kts
@@ -0,0 +1,18 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+    }
+}
+
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+rootProject.name = "common-build-src"
diff --git a/commonBuildSrc/src/main/kotlin/Dependencies.kt b/commonBuildSrc/src/main/kotlin/Dependencies.kt
new file mode 100644
index 0000000..b93dbcf
--- /dev/null
+++ b/commonBuildSrc/src/main/kotlin/Dependencies.kt
@@ -0,0 +1,63 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.JavaVersion
+import java.io.File
+
+class DependenciesPlugin: Plugin<Project> {
+  override fun apply(target: Project) {
+    // Intentionally empty
+  }
+}
+
+fun Project.getRoot() : File {
+  var parent = this.projectDir
+  while (!parent.getName().equals("d8_r8")) {
+    parent = parent.getParentFile()
+  }
+  return parent.getParentFile()
+}
+
+fun File.resolveAll(vararg xs: String) : File {
+  var that = this;
+  for (x in xs) {
+    that = that.resolve(x)
+  }
+  return that
+}
+
+object JvmCompatibility {
+  val sourceCompatibility = JavaVersion.VERSION_11
+  val targetCompatibility = JavaVersion.VERSION_11
+}
+
+object Versions {
+  const val asmVersion = "9.4"
+  const val fastUtilVersion = "7.2.0"
+  const val gsonVersion = "2.7"
+  const val guavaVersion = "30.1.1-jre"
+  const val joptSimpleVersion = "4.6"
+  const val junitVersion = "4.13-beta-2"
+  const val kotlinVersion = "1.8.0"
+  const val kotlinMetadataVersion = "0.6.0"
+  const val smaliVersion = "2.2b4"
+}
+
+object Deps {
+  val asm by lazy { "org.ow2.asm:asm:${Versions.asmVersion}" }
+  val asmUtil by lazy { "org.ow2.asm:asm-util:${Versions.asmVersion}" }
+  val asmCommons by lazy { "org.ow2.asm:asm-commons:${Versions.asmVersion}" }
+  val fastUtil by lazy { "it.unimi.dsi:fastutil:${Versions.fastUtilVersion}"}
+  val gson by lazy { "com.google.code.gson:gson:${Versions.gsonVersion}"}
+  val guava by lazy { "com.google.guava:guava:${Versions.guavaVersion}" }
+  val joptSimple by lazy { "net.sf.jopt-simple:jopt-simple:${Versions.joptSimpleVersion}" }
+  val junit by lazy { "junit:junit:${Versions.junitVersion}"}
+  val kotlinMetadata by lazy {
+    "org.jetbrains.kotlinx:kotlinx-metadata-jvm:${Versions.kotlinMetadataVersion}" }
+  val kotlinStdLib by lazy { "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinVersion}" }
+  val kotlinReflect by lazy { "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinVersion}" }
+  val smali by lazy { "org.smali:smali:${Versions.smaliVersion}" }
+}
diff --git a/d8_r8/gradle.properties b/d8_r8/gradle.properties
new file mode 100644
index 0000000..ef0be28
--- /dev/null
+++ b/d8_r8/gradle.properties
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true
+systemProp.file.encoding=UTF-8
+
+# Enable new incremental compilation
+kotlin.incremental.useClasspathSnapshot=true
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# Do not download any jdks or detect them. We provide them
+org.gradle.java.installations.auto-detect=false
+org.gradle.java.installations.auto-download=false
diff --git a/d8_r8/keepanno/build.gradle.kts b/d8_r8/keepanno/build.gradle.kts
new file mode 100644
index 0000000..b7a2acf
--- /dev/null
+++ b/d8_r8/keepanno/build.gradle.kts
@@ -0,0 +1,21 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+plugins {
+  `kotlin-dsl`
+  id("dependencies-plugin")
+}
+
+java {
+  sourceSets.main.configure {
+    java.srcDir(getRoot().resolveAll("src", "keepanno", "java"))
+  }
+  sourceCompatibility = JvmCompatibility.sourceCompatibility
+  targetCompatibility = JvmCompatibility.targetCompatibility
+}
+
+dependencies {
+  compileOnly(Deps.asm)
+  compileOnly(Deps.guava)
+}
diff --git a/d8_r8/keepanno/gradle.properties b/d8_r8/keepanno/gradle.properties
new file mode 100644
index 0000000..ef0be28
--- /dev/null
+++ b/d8_r8/keepanno/gradle.properties
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true
+systemProp.file.encoding=UTF-8
+
+# Enable new incremental compilation
+kotlin.incremental.useClasspathSnapshot=true
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# Do not download any jdks or detect them. We provide them
+org.gradle.java.installations.auto-detect=false
+org.gradle.java.installations.auto-download=false
diff --git a/d8_r8/keepanno/settings.gradle.kts b/d8_r8/keepanno/settings.gradle.kts
new file mode 100644
index 0000000..7b4d459
--- /dev/null
+++ b/d8_r8/keepanno/settings.gradle.kts
@@ -0,0 +1,18 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+    }
+}
+
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+rootProject.name = "keepanno"
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts
new file mode 100644
index 0000000..e664753
--- /dev/null
+++ b/d8_r8/main/build.gradle.kts
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+plugins {
+  `kotlin-dsl`
+  id("dependencies-plugin")
+}
+
+java {
+  sourceSets.main.configure {
+    java.srcDir(getRoot().resolveAll("src", "main", "java"))
+    resources.srcDirs(getRoot().resolveAll("third_party", "api_database", "api_database"))
+  }
+  sourceCompatibility = JvmCompatibility.sourceCompatibility
+  targetCompatibility = JvmCompatibility.targetCompatibility
+}
+
+dependencies {
+  implementation(":keepanno")
+  implementation(Deps.asm)
+  implementation(Deps.asmUtil)
+  implementation(Deps.asmCommons)
+  implementation(Deps.fastUtil)
+  implementation(Deps.gson)
+  implementation(Deps.guava)
+  implementation(Deps.joptSimple)
+  implementation(Deps.kotlinMetadata)
+}
+
+tasks.withType<JavaCompile> {
+  println("NOTE: Running with JDK: " + org.gradle.internal.jvm.Jvm.current().javaHome)
+}
diff --git a/d8_r8/main/gradle.properties b/d8_r8/main/gradle.properties
new file mode 100644
index 0000000..ef0be28
--- /dev/null
+++ b/d8_r8/main/gradle.properties
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true
+systemProp.file.encoding=UTF-8
+
+# Enable new incremental compilation
+kotlin.incremental.useClasspathSnapshot=true
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# Do not download any jdks or detect them. We provide them
+org.gradle.java.installations.auto-detect=false
+org.gradle.java.installations.auto-download=false
diff --git a/d8_r8/main/settings.gradle.kts b/d8_r8/main/settings.gradle.kts
new file mode 100644
index 0000000..2d727f0
--- /dev/null
+++ b/d8_r8/main/settings.gradle.kts
@@ -0,0 +1,18 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+    }
+}
+
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+rootProject.name = "r8"
diff --git a/d8_r8/settings.gradle.kts b/d8_r8/settings.gradle.kts
new file mode 100644
index 0000000..2feb93c
--- /dev/null
+++ b/d8_r8/settings.gradle.kts
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(X): Move this file out the repository root when old gradle is removed.
+
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+    }
+}
+
+dependencyResolutionManagement {
+  repositories {
+    mavenCentral()
+    gradlePluginPortal()
+  }
+}
+
+rootProject.name = "d8-r8"
+
+// This project is temporarily located in d8_r8. When moved to root, the parent
+// folder should just be removed.
+includeBuild(rootProject.projectDir.parentFile.resolve("commonBuildSrc"))
+includeBuild("keepanno")
+
+// We need to include src/main as a composite-build otherwise our test-modules
+// will compete with the test to compile the source files.
+includeBuild("main")
+includeBuild("test")
diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts
new file mode 100644
index 0000000..34f153b
--- /dev/null
+++ b/d8_r8/test/build.gradle.kts
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+plugins {
+  `kotlin-dsl`
+  id("dependencies-plugin")
+}
+
+val root = getRoot();
+
+java {
+  sourceSets.test.configure {
+    java.srcDir(root.resolveAll("src", "test", "java"))
+  }
+  sourceCompatibility = JvmCompatibility.sourceCompatibility
+  targetCompatibility = JvmCompatibility.targetCompatibility
+}
+
+// We cannot use languageVersion.set(JavaLanguageVersion.of("8")) because gradle cannot figure
+// out that the jdk is 1_8 and will try to download it.
+tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
+  kotlinOptions {
+    jvmTarget = "11"
+  }
+}
+
+
+dependencies {
+  implementation(":r8")
+  implementation(":keepanno")
+  implementation(Deps.asm)
+  implementation(Deps.gson)
+  implementation(Deps.guava)
+  implementation(Deps.junit)
+  implementation(Deps.kotlinStdLib)
+  implementation(Deps.kotlinReflect)
+  implementation(Deps.kotlinMetadata)
+  implementation(files(root.resolveAll("third_party", "ddmlib", "ddmlib.jar")))
+  implementation(
+    files(
+      root.resolveAll("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar")))
+  implementation(files(root.resolveAll("third_party", "jasmin", "jasmin-2.4.jar")))
+  implementation(Deps.fastUtil)
+  implementation(Deps.smali)
+  implementation(Deps.asmUtil)
+}
+
+tasks.named("test") {
+  dependsOn(gradle.includedBuild("tests_java_8").task(":compileJava"))
+}
+
+tasks.withType<Test> {
+  environment("USE_NEW_GRADLE_SETUP", "true")
+}
diff --git a/d8_r8/test/gradle.properties b/d8_r8/test/gradle.properties
new file mode 100644
index 0000000..ef0be28
--- /dev/null
+++ b/d8_r8/test/gradle.properties
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true
+systemProp.file.encoding=UTF-8
+
+# Enable new incremental compilation
+kotlin.incremental.useClasspathSnapshot=true
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# Do not download any jdks or detect them. We provide them
+org.gradle.java.installations.auto-detect=false
+org.gradle.java.installations.auto-download=false
diff --git a/d8_r8/test/settings.gradle.kts b/d8_r8/test/settings.gradle.kts
new file mode 100644
index 0000000..3138d52
--- /dev/null
+++ b/d8_r8/test/settings.gradle.kts
@@ -0,0 +1,15 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+rootProject.name = "r8-tests"
+
+val root = rootProject.projectDir.parentFile
+includeBuild(root.resolve("test_modules").resolve("tests_java_8"))
diff --git a/d8_r8/test_modules/tests_java_8/build.gradle.kts b/d8_r8/test_modules/tests_java_8/build.gradle.kts
new file mode 100644
index 0000000..9908599
--- /dev/null
+++ b/d8_r8/test_modules/tests_java_8/build.gradle.kts
@@ -0,0 +1,88 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import org.gradle.api.JavaVersion
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+  `kotlin-dsl`
+  `java-library`
+  id("dependencies-plugin")
+}
+
+val root = getRoot()
+
+java {
+  sourceSets.main.configure {
+    java.srcDir(root.resolveAll("src", "test", "java"))
+  }
+  sourceCompatibility = JavaVersion.VERSION_1_8
+  targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+
+// We cannot use languageVersion.set(JavaLanguageVersion.of("8")) because gradle cannot figure
+// out that the jdk is 1_8 and will try to download it.
+tasks.withType<KotlinCompile> {
+  kotlinOptions {
+    jvmTarget = "1.8"
+  }
+}
+
+// The test module compilation depends on main and keep anno output, but we cannot directly
+// reference the task we only obtain a task reference. To obtain the actual reference by creating
+// a dummy.
+tasks.register("dummy-keepanno-reference") {
+  dependsOn(gradle.includedBuild("keepanno").task(":jar"))
+}
+val keepAnnoTask = tasks.getByName("dummy-keepanno-reference")
+  .taskDependencies
+  .getDependencies(tasks.getByName("dummy-keepanno-reference"))
+  .iterator()
+  .next()
+
+tasks.register("dummy-r8-reference") {
+  dependsOn(gradle.includedBuild("main").task(":jar"))
+}
+val r8Task = tasks.getByName("dummy-r8-reference")
+  .taskDependencies
+  .getDependencies(tasks.getByName("dummy-r8-reference"))
+  .iterator()
+  .next()
+
+dependencies {
+  implementation(keepAnnoTask.outputs.files)
+  implementation(r8Task.outputs.files)
+  implementation(Deps.asm)
+  implementation(Deps.gson)
+  implementation(Deps.guava)
+  implementation(Deps.junit)
+  implementation(Deps.kotlinStdLib)
+  implementation(Deps.kotlinReflect)
+  implementation(Deps.kotlinMetadata)
+  implementation(files(root.resolveAll("third_party", "ddmlib", "ddmlib.jar")))
+  implementation(
+    files(
+      root.resolveAll("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar")))
+  implementation(files(root.resolveAll("third_party", "jasmin", "jasmin-2.4.jar")))
+  implementation(Deps.fastUtil)
+  implementation(Deps.smali)
+  implementation(Deps.asmUtil)
+}
+
+tasks.withType<JavaCompile> {
+  dependsOn(keepAnnoTask)
+  dependsOn(r8Task)
+  options.setFork(true)
+  options.forkOptions.memoryMaximumSize = "3g"
+  options.forkOptions.jvmArgs = listOf(
+    "-Xss256m",
+    // Set the bootclass path so compilation is consistent with 1.8 target compatibility.
+    "-Xbootclasspath/a:third_party/openjdk/openjdk-rt-1.8/rt.jar")
+}
+
+tasks.withType<KotlinCompile> {
+    dependsOn(keepAnnoTask)
+    dependsOn(r8Task)
+}
diff --git a/d8_r8/test_modules/tests_java_8/gradle.properties b/d8_r8/test_modules/tests_java_8/gradle.properties
new file mode 100644
index 0000000..ef0be28
--- /dev/null
+++ b/d8_r8/test_modules/tests_java_8/gradle.properties
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true
+systemProp.file.encoding=UTF-8
+
+# Enable new incremental compilation
+kotlin.incremental.useClasspathSnapshot=true
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# Do not download any jdks or detect them. We provide them
+org.gradle.java.installations.auto-detect=false
+org.gradle.java.installations.auto-download=false
diff --git a/d8_r8/test_modules/tests_java_8/settings.gradle.kts b/d8_r8/test_modules/tests_java_8/settings.gradle.kts
new file mode 100644
index 0000000..290b820
--- /dev/null
+++ b/d8_r8/test_modules/tests_java_8/settings.gradle.kts
@@ -0,0 +1,28 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+pluginManagement {
+  repositories {
+    gradlePluginPortal()
+  }
+}
+
+dependencyResolutionManagement {
+  repositories {
+    mavenCentral()
+    gradlePluginPortal()
+  }
+}
+
+rootProject.name = "r8-java8-tests"
+
+val d8Root = rootProject.projectDir.parentFile.parentFile
+val root = d8Root.parentFile
+
+includeBuild(root.resolve("commonBuildSrc"))
+includeBuild(d8Root.resolve("keepanno"))
+
+// We need to include src/main as a composite-build otherwise our test-modules
+// will compete with the test to compile the source files.
+includeBuild(d8Root.resolve("main"))
diff --git a/gradle.properties b/gradle.properties
index 41fabcc..b2b8d2a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,4 +2,16 @@
 # 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.
 
-org.gradle.jvmargs=-Xmx2048M
\ No newline at end of file
+org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
+kotlin.daemon.jvmargs=-Xmx3g -Dkotlin.js.compiler.legacy.force_enabled=true
+systemProp.file.encoding=UTF-8
+
+# Enable new incremental compilation
+kotlin.incremental.useClasspathSnapshot=true
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# Do not download any jdks or detect them. We provide them
+org.gradle.java.installations.auto-detect=false
+org.gradle.java.installations.auto-download=false
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 4d1269c..f251ecb 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.DumpInputFlags;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ProgramConsumerUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -154,6 +155,9 @@
         .setOptimizeMultidexForLinearAlloc(isOptimizeMultidexForLinearAlloc())
         .setThreadCount(getThreadCount())
         .setDesugarState(getDesugarState())
+        .setArtProfileProviders(
+            ListUtils.map(
+                getArtProfilesForRewriting(), ArtProfileForRewriting::getArtProfileProvider))
         .setStartupProfileProviders(getStartupProfileProviders());
     if (getAndroidPlatformBuild()) {
       builder.setAndroidPlatformBuild(true);
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index fa3e5c4..79330fb 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.utils.ArchiveBuilder;
+import com.android.tools.r8.utils.ClassFileConsumerDataImpl;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DirectoryBuilder;
 import com.android.tools.r8.utils.OutputBuilder;
@@ -36,22 +37,28 @@
    * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
    * then the compiler guaranties to exit with an error.
    *
-   * <p>The {@link ByteDataView} {@param data} object can only be assumed valid during the duration
-   * of the accept. If the bytes are needed beyond that, a copy must be made elsewhere.
+   * <p>The {@link ByteDataView} obtained by way of {@param data} object can only be assumed valid
+   * during the duration of the accept. If the bytes are needed beyond that, a copy must be made
+   * elsewhere.
    *
-   * @param data Java class-file encoded data.
-   * @param descriptor Class descriptor of the class the data pertains to.
-   * @param handler Diagnostics handler for reporting.
+   * @param data Java class-file encoded data and its meta-data.
    */
-  void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler);
+  default void acceptClassFile(ClassFileConsumerData data) {
+    accept(data.getByteDataView(), data.getClassDescriptor(), data.getDiagnosticsHandler());
+  }
+
+  @Deprecated
+  default void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+    acceptClassFile(new ClassFileConsumerDataImpl(data, descriptor, handler));
+  }
 
   /** Empty consumer to request the production of the resource but ignore its value. */
   static ClassFileConsumer emptyConsumer() {
     return ForwardingConsumer.EMPTY_CONSUMER;
   }
 
-  /** Forwarding consumer to delegate to an optional existing consumer. */
   @Keep
+  @Deprecated
   class ForwardingConsumer implements ClassFileConsumer {
 
     private static final ClassFileConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
@@ -68,9 +75,9 @@
     }
 
     @Override
-    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+    public void acceptClassFile(ClassFileConsumerData data) {
       if (consumer != null) {
-        consumer.accept(data, descriptor, handler);
+        consumer.acceptClassFile(data);
       }
     }
 
@@ -117,9 +124,12 @@
     }
 
     @Override
-    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-      super.accept(data, descriptor, handler);
-      outputBuilder.addFile(DescriptorUtils.getClassFileName(descriptor), data, handler);
+    public void acceptClassFile(ClassFileConsumerData data) {
+      super.acceptClassFile(data);
+      outputBuilder.addFile(
+          DescriptorUtils.getClassFileName(data.getClassDescriptor()),
+          data.getByteDataView(),
+          data.getDiagnosticsHandler());
     }
 
     @Override
@@ -194,9 +204,12 @@
     }
 
     @Override
-    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-      super.accept(data, descriptor, handler);
-      outputBuilder.addFile(DescriptorUtils.getClassFileName(descriptor), data, handler);
+    public void acceptClassFile(ClassFileConsumerData data) {
+      super.acceptClassFile(data);
+      outputBuilder.addFile(
+          DescriptorUtils.getClassFileName(data.getClassDescriptor()),
+          data.getByteDataView(),
+          data.getDiagnosticsHandler());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumerData.java b/src/main/java/com/android/tools/r8/ClassFileConsumerData.java
new file mode 100644
index 0000000..eeef801
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumerData.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+/** Data provided in the primary callback of {@link ClassFileConsumer}. */
+@Keep
+public interface ClassFileConsumerData {
+
+  /**
+   * View of the Java class-file encoded data.
+   *
+   * <p>The obtained {@link ByteDataView} object can only be assumed valid during the duration of
+   * the accept method. If the bytes are needed beyond that, a copy must be made elsewhere.
+   */
+  ByteDataView getByteDataView();
+
+  /** Copy of the bytes for the Java class-file encoded data. */
+  byte[] getByteDataCopy();
+
+  /** Class descriptor of the class the data pertains to. */
+  String getClassDescriptor();
+
+  /** Diagnostics handler for reporting. */
+  DiagnosticsHandler getDiagnosticsHandler();
+}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index c57bc22..85a0fbf 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -53,7 +53,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -441,12 +440,14 @@
     private final List<ProgramResource> resources = new ArrayList<>();
 
     @Override
-    public synchronized void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+    public synchronized void acceptDexIndexedFile(DexIndexedConsumerData data) {
       // TODO(b/154106502): Map Origin information.
       resources.add(
           ProgramResource.fromBytes(
-              Origin.unknown(), ProgramResource.Kind.DEX, data.copyByteData(), descriptors));
+              Origin.unknown(),
+              ProgramResource.Kind.DEX,
+              data.getByteDataCopy(),
+              data.getClassDescriptors()));
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index a12e730..13c12a2 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -402,12 +402,16 @@
           && isMinApiLevelSet()) {
         reporter.error("Compiling to CF with --min-api and --no-desugaring is not supported");
       }
-      if (!getStartupProfileProviders().isEmpty()
-          && getMinApiLevel() < AndroidApiLevel.L.getLevel()) {
-        reporter.error(
-            "D8 startup layout requires native multi dex support (API level "
-                + AndroidApiLevel.L.getLevel()
-                + " and above)");
+      if (!getStartupProfileProviders().isEmpty()) {
+        if (intermediate) {
+          reporter.error("D8 startup layout is not supported in intermediate mode");
+        }
+        if (getMinApiLevel() < AndroidApiLevel.L.getLevel()) {
+          reporter.error(
+              "D8 startup layout requires native multi dex support (API level "
+                  + AndroidApiLevel.L.getLevel()
+                  + " and above)");
+        }
       }
       super.validate();
     }
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
index 1675cb2..68e6f12 100644
--- a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumer.java
@@ -34,7 +34,7 @@
 @KeepForSubclassing
 public interface DexFilePerClassFileConsumer extends ProgramConsumer, ByteBufferProvider {
 
-  static final boolean SHOULD_COMBINE_SYNTHETIC_CLASSES = true;
+  boolean SHOULD_COMBINE_SYNTHETIC_CLASSES = true;
 
   /**
    * Callback to receive DEX data for a single Java class-file input and its companion classes.
@@ -42,24 +42,30 @@
    * <p>There is no guaranteed order and files might be written concurrently.
    *
    * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
-   * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
-   * then the compiler guaranties to exit with an error.
+   * {@link DiagnosticsHandler} in the callback data. If an error is reported via handler and no
+   * exceptions are thrown, then the compiler guaranties to exit with an error.
    *
-   * <p>The {@link ByteDataView} {@param data} object can only be assumed valid during the duration
-   * of the accept. If the bytes are needed beyond that, a copy must be made elsewhere.
-   *
-   * @param primaryClassDescriptor Class descriptor of the class from the input class-file.
-   * @param data DEX encoded data in a ByteDataView wrapper.
-   * @param descriptors Class descriptors for all classes defined in the DEX data.
-   * @param handler Diagnostics handler for reporting.
+   * <p>The {@link ByteDataView} obtained from the {@param data} object can only be assumed valid
+   * during the duration of the accept. If the bytes are needed beyond that, a copy must be made
+   * elsewhere.
    */
+  default void acceptDexFile(DexFilePerClassFileConsumerData data) {
+    accept(
+        data.getPrimaryClassDescriptor(),
+        data.getByteDataView(),
+        data.getClassDescriptors(),
+        data.getDiagnosticsHandler());
+  }
+
+  // Any new implementation should not use or call the deprecated accept method.
+  @Deprecated
   default void accept(
       String primaryClassDescriptor,
       ByteDataView data,
       Set<String> descriptors,
       DiagnosticsHandler handler) {
-    // To avoid breaking binary compatiblity, old consumers not implementing the new API will be
-    // forwarded to. New consumers must implement the accept on ByteDataView.
+    // To avoid breaking binary compatibility, old consumers not implementing the new API will be
+    // forwarded to. New consumers must implement the accept method on the data object.
     accept(primaryClassDescriptor, data.copyByteData(), descriptors, handler);
   }
 
@@ -92,8 +98,8 @@
     return ForwardingConsumer.EMPTY_CONSUMER;
   }
 
-  /** Forwarding consumer to delegate to an optional existing consumer. */
   @Keep
+  @Deprecated
   class ForwardingConsumer implements DexFilePerClassFileConsumer {
 
     private static final DexFilePerClassFileConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
@@ -110,13 +116,9 @@
     }
 
     @Override
-    public void accept(
-        String primaryClassDescriptor,
-        ByteDataView data,
-        Set<String> descriptors,
-        DiagnosticsHandler handler) {
+    public void acceptDexFile(DexFilePerClassFileConsumerData data) {
       if (consumer != null) {
-        consumer.accept(primaryClassDescriptor, data, descriptors, handler);
+        consumer.acceptDexFile(data);
       }
     }
 
@@ -178,13 +180,12 @@
     }
 
     @Override
-    public void accept(
-        String primaryClassDescriptor,
-        ByteDataView data,
-        Set<String> descriptors,
-        DiagnosticsHandler handler) {
-      super.accept(primaryClassDescriptor, data, descriptors, handler);
-      outputBuilder.addFile(getDexFileName(primaryClassDescriptor), data, handler);
+    public void acceptDexFile(DexFilePerClassFileConsumerData data) {
+      super.acceptDexFile(data);
+      outputBuilder.addFile(
+          getDexFileName(data.getPrimaryClassDescriptor()),
+          data.getByteDataView(),
+          data.getDiagnosticsHandler());
     }
 
     @Override
@@ -262,13 +263,12 @@
     }
 
     @Override
-    public void accept(
-        String primaryClassDescriptor,
-        ByteDataView data,
-        Set<String> descriptors,
-        DiagnosticsHandler handler) {
-      super.accept(primaryClassDescriptor, data, descriptors, handler);
-      outputBuilder.addFile(getDexFileName(primaryClassDescriptor), data, handler);
+    public void acceptDexFile(DexFilePerClassFileConsumerData data) {
+      super.acceptDexFile(data);
+      outputBuilder.addFile(
+          getDexFileName(data.getPrimaryClassDescriptor()),
+          data.getByteDataView(),
+          data.getDiagnosticsHandler());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumerData.java b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumerData.java
new file mode 100644
index 0000000..fba6b8d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DexFilePerClassFileConsumerData.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.util.Set;
+
+/** Data provided in the primary callback of {@link DexFilePerClassFileConsumer}. */
+@Keep
+public interface DexFilePerClassFileConsumerData {
+
+  /** Class descriptor of the class from the input class-file. */
+  String getPrimaryClassDescriptor();
+
+  /** DEX encoded data in a ByteDataView wrapper. */
+  ByteDataView getByteDataView();
+
+  /** Copy of the bytes for the DEX encoded data. */
+  byte[] getByteDataCopy();
+
+  /** Class descriptors for all classes defined in the DEX data. */
+  Set<String> getClassDescriptors();
+
+  /** Diagnostics handler for reporting. */
+  DiagnosticsHandler getDiagnosticsHandler();
+
+  /**
+   * Class descriptor of the primary-class's synthetic context.
+   *
+   * <p>If primary class is a compiler-synthesized class (i.e. it is an input that was synthesized
+   * by a prior D8 intermediate compilation) then the value is the descriptor of the class that
+   * caused the primary class to be synthesized. The value is null in all other cases.
+   */
+  String getSynthesizingContextForPrimaryClass();
+}
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 39f5b2e..cbccf59 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -46,17 +46,22 @@
    * <p>There is no guaranteed order and files might be written concurrently.
    *
    * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
-   * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
-   * then the compiler guaranties to exit with an error.
+   * {@link DiagnosticsHandler} in the callback data. If an error is reported via handler and no
+   * exceptions are thrown, then the compiler guaranties to exit with an error.
    *
-   * <p>The {@link ByteDataView} {@param data} object can only be assumed valid during the duration
-   * of the accept. If the bytes are needed beyond that, a copy must be made elsewhere.
-   *
-   * @param fileIndex Index of the DEX file for multi-dexing. Files are zero-indexed.
-   * @param data DEX encoded data in a ByteDataView wrapper.
-   * @param descriptors Class descriptors for all classes defined in the DEX data.
-   * @param handler Diagnostics handler for reporting.
+   * <p>The {@link ByteDataView} obtained from the {@param data} object can only be assumed valid
+   * during the duration of the accept. If the bytes are needed beyond that, a copy must be made
+   * elsewhere.
    */
+  default void acceptDexIndexedFile(DexIndexedConsumerData data) {
+    accept(
+        data.getFileIndex(),
+        data.getByteDataView(),
+        data.getClassDescriptors(),
+        data.getDiagnosticsHandler());
+  }
+
+  @Deprecated
   default void accept(
       int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
     // To avoid breaking binary compatiblity, old consumers not implementing the new API will be
@@ -77,8 +82,8 @@
     return ForwardingConsumer.EMPTY_CONSUMER;
   }
 
-  /** Forwarding consumer to delegate to an optional existing consumer. */
   @Keep
+  @Deprecated
   class ForwardingConsumer implements DexIndexedConsumer {
 
     private static final DexIndexedConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
@@ -95,10 +100,9 @@
     }
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
       if (consumer != null) {
-        consumer.accept(fileIndex, data, descriptors, handler);
+        consumer.acceptDexIndexedFile(data);
       }
     }
 
@@ -150,11 +154,13 @@
     }
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
-      super.accept(fileIndex, data, descriptors, handler);
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
+      super.acceptDexIndexedFile(data);
       outputBuilder.addIndexedClassFile(
-          fileIndex, DexUtils.getDefaultDexFileName(fileIndex), data, handler);
+          data.getFileIndex(),
+          DexUtils.getDefaultDexFileName(data.getFileIndex()),
+          data.getByteDataView(),
+          data.getDiagnosticsHandler());
     }
 
     @Override
@@ -245,15 +251,17 @@
     }
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
-      super.accept(fileIndex, data, descriptors, handler);
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
+      super.acceptDexIndexedFile(data);
       try {
         prepareDirectory();
       } catch (IOException e) {
-        handler.error(new ExceptionDiagnostic(e, new PathOrigin(directory)));
+        data.getDiagnosticsHandler().error(new ExceptionDiagnostic(e, new PathOrigin(directory)));
       }
-      outputBuilder.addFile(DexUtils.getDefaultDexFileName(fileIndex), data, handler);
+      outputBuilder.addFile(
+          DexUtils.getDefaultDexFileName(data.getFileIndex()),
+          data.getByteDataView(),
+          data.getDiagnosticsHandler());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumerData.java b/src/main/java/com/android/tools/r8/DexIndexedConsumerData.java
new file mode 100644
index 0000000..1c46f8c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumerData.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.util.Set;
+
+/** Data provided in the primary callback of {@link DexIndexedConsumer}. */
+@Keep
+public interface DexIndexedConsumerData {
+
+  /** Index of the DEX file for multi-dexing (Files are zero-indexed). */
+  int getFileIndex();
+
+  /** DEX encoded data in a ByteDataView wrapper. */
+  ByteDataView getByteDataView();
+
+  /** Copy of the bytes for the DEX encoded data. */
+  byte[] getByteDataCopy();
+
+  /** Class descriptors for all classes defined in the DEX data. */
+  Set<String> getClassDescriptors();
+
+  /** Diagnostics handler for reporting. */
+  DiagnosticsHandler getDiagnosticsHandler();
+}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index ebe84b2..6bac5df 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -164,7 +164,13 @@
 
   @Override
   List<ArtProfileForRewriting> getArtProfilesForRewriting() {
-    return getD8Command().getArtProfilesForRewriting();
+    if (getD8Command() != null) {
+      return getD8Command().getArtProfilesForRewriting();
+    }
+    if (getR8Command() != null) {
+      return getR8Command().getArtProfilesForRewriting();
+    }
+    return Collections.emptyList();
   }
 
   @Override
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 99cb304..d0daf58 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -58,6 +58,8 @@
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexFilePerClassFileConsumerDataImpl;
+import com.android.tools.r8.utils.DexIndexedConsumerDataImpl;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer;
@@ -586,13 +588,14 @@
     ProgramConsumer consumer;
     ByteBufferProvider byteBufferProvider;
 
+    String primaryClassDescriptor = virtualFile.getPrimaryClassDescriptor();
     if (globalSyntheticFiles != null && globalSyntheticFiles.contains(virtualFile)) {
       consumer = globalsSyntheticsConsumer;
       byteBufferProvider = globalsSyntheticsConsumer;
     } else if (programConsumer != null) {
       consumer = programConsumer;
       byteBufferProvider = programConsumer;
-    } else if (virtualFile.getPrimaryClassDescriptor() != null) {
+    } else if (primaryClassDescriptor != null) {
       consumer = options.getDexFilePerClassFileConsumer();
       byteBufferProvider = options.getDexFilePerClassFileConsumer();
     } else {
@@ -620,14 +623,18 @@
     timing.begin("Pass bytes to consumer");
     if (consumer instanceof DexFilePerClassFileConsumer) {
       ((DexFilePerClassFileConsumer) consumer)
-          .accept(
-              virtualFile.getPrimaryClassDescriptor(),
-              data,
-              virtualFile.getClassDescriptors(),
-              options.reporter);
+          .acceptDexFile(
+              new DexFilePerClassFileConsumerDataImpl(
+                  primaryClassDescriptor,
+                  virtualFile.getPrimaryClassSynthesizingContextDescriptor(),
+                  data,
+                  virtualFile.getClassDescriptors(),
+                  options.reporter));
     } else {
       ((DexIndexedConsumer) consumer)
-          .accept(virtualFile.getId(), data, virtualFile.getClassDescriptors(), options.reporter);
+          .acceptDexIndexedFile(
+              new DexIndexedConsumerDataImpl(
+                  virtualFile.getId(), data, virtualFile.getClassDescriptors(), options.reporter));
     }
     timing.end();
     // Release use of the backing buffer now that accept has returned.
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
index 0a81bd2..cdb74b5 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.utils.BitUtils;
+import com.android.tools.r8.utils.DexIndexedConsumerDataImpl;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -172,7 +173,9 @@
     if (consumer instanceof DexFilePerClassFileConsumer) {
       assert false;
     } else {
-      ((DexIndexedConsumer) consumer).accept(0, data, Sets.newIdentityHashSet(), options.reporter);
+      ((DexIndexedConsumer) consumer)
+          .acceptDexIndexedFile(
+              new DexIndexedConsumerDataImpl(0, data, Sets.newIdentityHashSet(), options.reporter));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 6d1af5d..692192d 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -49,7 +49,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -73,6 +72,7 @@
   private final StartupOrder startupOrder;
 
   private final DexString primaryClassDescriptor;
+  private final DexString primaryClassSynthesizingContextDescriptor;
   private DebugRepresentation debugRepresentation;
 
   VirtualFile(int id, AppView<?> appView) {
@@ -102,12 +102,23 @@
     this.id = id;
     this.indexedItems = new VirtualFileIndexedItemCollection(appView);
     this.transaction = new IndexedItemTransaction(indexedItems, appView);
-    this.primaryClassDescriptor =
-        primaryClass == null
-            ? null
-            : appView.getNamingLens().lookupClassDescriptor(primaryClass.type);
     this.featureSplit = featureSplit;
     this.startupOrder = startupOrder;
+    if (primaryClass == null) {
+      primaryClassDescriptor = null;
+      primaryClassSynthesizingContextDescriptor = null;
+    } else {
+      DexType type = primaryClass.getType();
+      primaryClassDescriptor = appView.getNamingLens().lookupClassDescriptor(type);
+      Collection<DexType> contexts = appView.getSyntheticItems().getSynthesizingContextTypes(type);
+      if (contexts.size() == 1) {
+        primaryClassSynthesizingContextDescriptor =
+            appView.getNamingLens().lookupClassDescriptor(contexts.iterator().next());
+      } else {
+        assert contexts.isEmpty();
+        primaryClassSynthesizingContextDescriptor = null;
+      }
+    }
   }
 
   public int getId() {
@@ -135,6 +146,12 @@
     return primaryClassDescriptor == null ? null : primaryClassDescriptor.toString();
   }
 
+  public String getPrimaryClassSynthesizingContextDescriptor() {
+    return primaryClassSynthesizingContextDescriptor == null
+        ? null
+        : primaryClassSynthesizingContextDescriptor.toString();
+  }
+
   public void setDebugRepresentation(DebugRepresentation debugRepresentation) {
     assert debugRepresentation != null;
     assert this.debugRepresentation == null;
@@ -319,35 +336,36 @@
 
     @Override
     public List<VirtualFile> run() {
-      HashMap<DexProgramClass, VirtualFile> files = new HashMap<>();
-      Collection<DexProgramClass> synthetics = new ArrayList<>();
+      Map<DexType, VirtualFile> files = new IdentityHashMap<>();
+      Map<DexType, List<DexProgramClass>> derivedSynthetics = new LinkedHashMap<>();
       // Assign dedicated virtual files for all program classes.
       for (DexProgramClass clazz : classes) {
-        // TODO(b/181636450): Simplify this making use of the assumption that synthetics are never
-        //  duplicated.
-        if (!combineSyntheticClassesWithPrimaryClass
-            || !appView.getSyntheticItems().isSyntheticClass(clazz)) {
-          VirtualFile file = new VirtualFile(virtualFiles.size(), appView, clazz);
-          virtualFiles.add(file);
-          file.addClass(clazz);
-          files.put(clazz, file);
-          // Commit this early, so that we do not keep the transaction state around longer than
-          // needed and clear the underlying sets.
-          file.commitTransaction();
-        } else {
-          synthetics.add(clazz);
+        if (combineSyntheticClassesWithPrimaryClass) {
+          DexType inputContextType =
+              appView
+                  .getSyntheticItems()
+                  .getSynthesizingInputContext(clazz.getType(), appView.options());
+          if (inputContextType != null && inputContextType != clazz.getType()) {
+            derivedSynthetics.computeIfAbsent(inputContextType, k -> new ArrayList<>()).add(clazz);
+            continue;
+          }
         }
-      }
-      for (DexProgramClass synthetic : synthetics) {
-        Collection<DexType> synthesizingContexts =
-            appView.getSyntheticItems().getSynthesizingContextTypes(synthetic.getType());
-        assert synthesizingContexts.size() == 1;
-        DexProgramClass inputType =
-            appView.definitionForProgramType(synthesizingContexts.iterator().next());
-        VirtualFile file = files.get(inputType);
-        file.addClass(synthetic);
+        VirtualFile file = new VirtualFile(virtualFiles.size(), appView, clazz);
+        virtualFiles.add(file);
+        file.addClass(clazz);
+        files.put(clazz.getType(), file);
+        // Commit this early, so that we do not keep the transaction state around longer than
+        // needed and clear the underlying sets.
         file.commitTransaction();
       }
+      derivedSynthetics.forEach(
+          (inputContextType, synthetics) -> {
+            VirtualFile file = files.get(inputContextType);
+            for (DexProgramClass synthetic : synthetics) {
+              file.addClass(synthetic);
+              file.commitTransaction();
+            }
+          });
       return virtualFiles;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 2ecf1d3..0c538fa 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.startup.StartupProfileProvider;
@@ -69,6 +70,7 @@
   private final FeatureSplitConfiguration featureSplitConfiguration;
   private final ProguardConfiguration proguardConfiguration;
   private final List<ProguardConfigurationRule> mainDexKeepRules;
+  private final Collection<ArtProfileProvider> artProfileProviders;
   private final Collection<StartupProfileProvider> startupProfileProviders;
   private final boolean enableMissingLibraryApiModeling;
   private final boolean isAndroidPlatformBuild;
@@ -98,6 +100,7 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       ProguardConfiguration proguardConfiguration,
       List<ProguardConfigurationRule> mainDexKeepRules,
+      Collection<ArtProfileProvider> artProfileProviders,
       Collection<StartupProfileProvider> startupProfileProviders,
       boolean enableMissingLibraryApiModeling,
       boolean isAndroidPlatformBuild,
@@ -120,6 +123,7 @@
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.proguardConfiguration = proguardConfiguration;
     this.mainDexKeepRules = mainDexKeepRules;
+    this.artProfileProviders = artProfileProviders;
     this.startupProfileProviders = startupProfileProviders;
     this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
     this.isAndroidPlatformBuild = isAndroidPlatformBuild;
@@ -296,6 +300,14 @@
     return mainDexKeepRules;
   }
 
+  public boolean hasArtProfileProviders() {
+    return artProfileProviders != null && !artProfileProviders.isEmpty();
+  }
+
+  public Collection<ArtProfileProvider> getArtProfileProviders() {
+    return artProfileProviders;
+  }
+
   public boolean hasStartupProfileProviders() {
     return startupProfileProviders != null && !startupProfileProviders.isEmpty();
   }
@@ -331,6 +343,7 @@
     private FeatureSplitConfiguration featureSplitConfiguration;
     private ProguardConfiguration proguardConfiguration;
     private List<ProguardConfigurationRule> mainDexKeepRules;
+    private Collection<ArtProfileProvider> artProfileProviders;
     private Collection<StartupProfileProvider> startupProfileProviders;
 
     private boolean enableMissingLibraryApiModeling = false;
@@ -437,6 +450,11 @@
       return this;
     }
 
+    public Builder setArtProfileProviders(Collection<ArtProfileProvider> artProfileProviders) {
+      this.artProfileProviders = artProfileProviders;
+      return this;
+    }
+
     public Builder setStartupProfileProviders(
         Collection<StartupProfileProvider> startupProfileProviders) {
       this.startupProfileProviders = startupProfileProviders;
@@ -497,6 +515,7 @@
           featureSplitConfiguration,
           proguardConfiguration,
           mainDexKeepRules,
+          artProfileProviders,
           startupProfileProviders,
           enableMissingLibraryApiModeling,
           isAndroidPlatformBuild,
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 5ab8746..ec98db1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,7 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.origin.GlobalSyntheticOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexInfo;
@@ -87,7 +88,7 @@
     return new AppInfo(app, syntheticItems, mainDexInfo, new BooleanBox());
   }
 
-  protected InternalOptions options() {
+  public InternalOptions options() {
     return app.options;
   }
 
@@ -174,19 +175,40 @@
     }
     DexClass definition = definitionFor(type);
     if (definition != null && !definition.isLibraryClass() && !dependent.isLibraryClass()) {
-      InterfaceMethodRewriter.reportDependencyEdge(dependent, definition, this);
+      reportDependencyEdge(dependent, definition);
     }
     return definition;
   }
 
-  public DexProgramClass unsafeDirectProgramTypeLookup(DexType type) {
-    return app.programDefinitionFor(type);
+  public void reportDependencyEdge(DexClass dependent, DexClass dependency) {
+    assert !dependent.isLibraryClass();
+    assert !dependency.isLibraryClass();
+    DesugarGraphConsumer consumer = options().desugarGraphConsumer;
+    if (consumer == null) {
+      return;
+    }
+    Origin dependencyOrigin = dependency.getOrigin();
+    Collection<Origin> dependentOrigins =
+        getSyntheticItems().getSynthesizingOrigin(dependent.getType());
+    if (dependentOrigins.isEmpty()) {
+      reportDependencyEdge(consumer, dependencyOrigin, dependent.getOrigin());
+    } else {
+      for (Origin dependentOrigin : dependentOrigins) {
+        reportDependencyEdge(consumer, dependencyOrigin, dependentOrigin);
+      }
+    }
   }
 
-  public Origin originFor(DexType type) {
-    assert checkIfObsolete();
-    DexClass definition = app.definitionFor(type);
-    return definition == null ? Origin.unknown() : definition.origin;
+  private void reportDependencyEdge(
+      DesugarGraphConsumer consumer, Origin dependencyOrigin, Origin dependentOrigin) {
+    if (dependencyOrigin == GlobalSyntheticOrigin.instance()
+        || dependentOrigin == GlobalSyntheticOrigin.instance()) {
+      // D8/R8 does not report edges to synthetic classes that D8/R8 generates.
+      return;
+    }
+    if (dependentOrigin != dependencyOrigin) {
+      consumer.accept(dependentOrigin, dependencyOrigin);
+    }
   }
 
   /**
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 fe3a2b4..40e5b9f 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
@@ -101,13 +101,24 @@
     if (appView.options().enableTryWithResourcesDesugaring()) {
       desugarings.add(new TwrInstructionDesugaring(appView));
     }
+    recordRewriter = RecordDesugaring.create(appView);
+    if (recordRewriter != null) {
+      desugarings.add(recordRewriter);
+    }
+    StringConcatInstructionDesugaring stringConcatDesugaring =
+        new StringConcatInstructionDesugaring(appView);
+    desugarings.add(stringConcatDesugaring);
+    LambdaInstructionDesugaring lambdaDesugaring = new LambdaInstructionDesugaring(appView);
+    desugarings.add(lambdaDesugaring);
     interfaceMethodRewriter =
         InterfaceMethodRewriter.create(
             appView,
             SetUtils.newImmutableSetExcludingNullItems(
                 alwaysThrowingInstructionDesugaring,
                 backportedMethodRewriter,
-                desugaredLibraryRetargeter));
+                desugaredLibraryRetargeter),
+            SetUtils.newImmutableSetExcludingNullItems(
+                lambdaDesugaring, stringConcatDesugaring, recordRewriter));
     if (interfaceMethodRewriter != null) {
       desugarings.add(interfaceMethodRewriter);
     }
@@ -124,7 +135,6 @@
     if (desugaredLibraryAPIConverter != null) {
       desugarings.add(desugaredLibraryAPIConverter);
     }
-    desugarings.add(new LambdaInstructionDesugaring(appView));
     desugarings.add(new ConstantDynamicInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     if (appView.options().isGeneratingClassFiles()) {
@@ -132,7 +142,6 @@
       assert nestBasedAccessDesugaring != null;
       desugarings.add(new InvokeToPrivateRewriter());
     }
-    desugarings.add(new StringConcatInstructionDesugaring(appView));
     if (appView.options().shouldDesugarBufferCovariantReturnType()) {
       desugarings.add(new BufferCovariantReturnTypeRewriter(appView));
     }
@@ -142,10 +151,6 @@
     if (nestBasedAccessDesugaring != null) {
       desugarings.add(nestBasedAccessDesugaring);
     }
-    this.recordRewriter = RecordDesugaring.create(appView);
-    if (recordRewriter != null) {
-      desugarings.add(recordRewriter);
-    }
     VarHandleDesugaring varHandleDesugaring = VarHandleDesugaring.create(appView);
     if (varHandleDesugaring != null) {
       desugarings.add(varHandleDesugaring);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 7695f1d..6bc5e1d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -9,10 +9,10 @@
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode.NONE;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.getInterfaceMethodDesugaringMode;
 
-import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
@@ -40,8 +40,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.icce.AlwaysThrowingInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode;
-import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
-import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -102,8 +100,9 @@
   // Caches default interface method info for already processed interfaces.
   private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
 
-  // This is used to filter out double desugaring on backported methods.
-  private final Set<CfInstructionDesugaring> precedingDesugarings;
+  // This is used to filter out double desugaring.
+  private final Set<CfInstructionDesugaring> precedingDesugaringsForInvoke;
+  private final Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic;
 
   /** Defines a minor variation in desugaring. */
   public enum Flavor {
@@ -114,21 +113,29 @@
   }
 
   public static InterfaceMethodRewriter create(
-      AppView<?> appView, Set<CfInstructionDesugaring> precedingDesugarings) {
+      AppView<?> appView,
+      Set<CfInstructionDesugaring> precedingDesugaringsForInvoke,
+      Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic) {
     InterfaceMethodDesugaringMode desugaringMode =
         getInterfaceMethodDesugaringMode(appView.options());
     if (desugaringMode == NONE) {
       return null;
     }
-    return new InterfaceMethodRewriter(appView, precedingDesugarings, desugaringMode);
+    return new InterfaceMethodRewriter(
+        appView,
+        precedingDesugaringsForInvoke,
+        precedingDesugaringsForInvokeDynamic,
+        desugaringMode);
   }
 
-  public InterfaceMethodRewriter(
+  private InterfaceMethodRewriter(
       AppView<?> appView,
-      Set<CfInstructionDesugaring> precedingDesugarings,
+      Set<CfInstructionDesugaring> precedingDesugaringsForInvoke,
+      Set<CfInstructionDesugaring> precedingDesugaringsForInvokeDynamic,
       InterfaceMethodDesugaringMode desugaringMode) {
     this.appView = appView;
-    this.precedingDesugarings = precedingDesugarings;
+    this.precedingDesugaringsForInvoke = precedingDesugaringsForInvoke;
+    this.precedingDesugaringsForInvokeDynamic = precedingDesugaringsForInvokeDynamic;
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
     assert desugaringMode == EMULATED_INTERFACE_ONLY || desugaringMode == ALL;
@@ -197,7 +204,14 @@
 
   private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
     return Iterables.any(
-        precedingDesugarings, desugaring -> desugaring.compute(invoke, context).needsDesugaring());
+        precedingDesugaringsForInvoke,
+        desugaring -> desugaring.compute(invoke, context).needsDesugaring());
+  }
+
+  private boolean isAlreadyDesugared(CfInvokeDynamic invoke, ProgramMethod context) {
+    return Iterables.any(
+        precedingDesugaringsForInvokeDynamic,
+        desugaring -> desugaring.compute(invoke, context).needsDesugaring());
   }
 
   @Override
@@ -221,9 +235,7 @@
     CfCode code = context.getDefinition().getCode().asCfCode();
     for (CfInstruction instruction : code.getInstructions()) {
       if (instruction.isInvokeDynamic()
-          && !LambdaInstructionDesugaring.isLambdaInvoke(instruction, context, appView)
-          && !StringConcatInstructionDesugaring.isStringConcatInvoke(
-              instruction, appView.dexItemFactory())) {
+          && !isAlreadyDesugared(instruction.asInvokeDynamic(), context)) {
         reportInterfaceMethodHandleCallSite(instruction.asInvokeDynamic().getCallSite(), context);
       }
       compute(instruction, context).scan();
@@ -940,7 +952,7 @@
     // At this point we likely have a non-library type that may depend on default method information
     // from its interfaces and the dependency should be reported.
     if (implementing.isProgramClass() && !definedInterface.isLibraryClass()) {
-      reportDependencyEdge(implementing.asProgramClass(), definedInterface, appView.appInfo());
+      appView.appInfo().reportDependencyEdge(implementing.asProgramClass(), definedInterface);
     }
 
     // Merge information from all superinterfaces.
@@ -974,31 +986,4 @@
     MethodPosition position = new MethodPosition(method.asMethodReference());
     options.warningMissingTypeForDesugar(origin, position, missing, method);
   }
-
-  public static void reportDependencyEdge(
-      DexClass dependent, DexClass dependency, AppInfo appInfo) {
-    assert !dependent.isLibraryClass();
-    assert !dependency.isLibraryClass();
-    DesugarGraphConsumer consumer = appInfo.app().options.desugarGraphConsumer;
-    if (consumer != null) {
-      Origin dependencyOrigin = dependency.getOrigin();
-      java.util.Collection<DexType> dependents =
-          appInfo.getSyntheticItems().getSynthesizingContextTypes(dependent.getType());
-      if (dependents.isEmpty()) {
-        reportDependencyEdge(consumer, dependencyOrigin, dependent);
-      } else {
-        for (DexType type : dependents) {
-          reportDependencyEdge(consumer, dependencyOrigin, appInfo.definitionFor(type));
-        }
-      }
-    }
-  }
-
-  private static void reportDependencyEdge(
-      DesugarGraphConsumer consumer, Origin dependencyOrigin, DexClass clazz) {
-    Origin dependentOrigin = clazz.getOrigin();
-    if (dependentOrigin != dependencyOrigin) {
-      consumer.accept(dependentOrigin, dependencyOrigin);
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index 19df173..c7e64b08 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.nest;
 
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.reportDependencyEdge;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.DexClass;
@@ -47,8 +45,8 @@
           DexClass hostClass = nest.getHostClass();
           for (DexClass memberClass : nest.getMembers()) {
             if (hostClass.isProgramClass() || memberClass.isProgramClass()) {
-              reportDependencyEdge(hostClass, memberClass, appView.appInfo());
-              reportDependencyEdge(memberClass, hostClass, appView.appInfo());
+              appView.appInfo().reportDependencyEdge(hostClass, memberClass);
+              appView.appInfo().reportDependencyEdge(memberClass, hostClass);
             }
           }
         },
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
index 0b29d7e..ac70a25 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
@@ -234,22 +234,6 @@
     return builder.desugar(localStackAllocator);
   }
 
-  public static boolean isStringConcatInvoke(CfInstruction instruction, DexItemFactory factory) {
-    CfInvokeDynamic invoke = instruction.asInvokeDynamic();
-    if (invoke == null) {
-      return false;
-    }
-    // We are interested in bootstrap methods StringConcatFactory::makeConcat
-    // and StringConcatFactory::makeConcatWithConstants, both are static.
-    DexCallSite callSite = invoke.getCallSite();
-    if (callSite.bootstrapMethod.type.isInvokeStatic()) {
-      DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
-      return bootstrapMethod == factory.stringConcatFactoryMembers.makeConcat
-          || bootstrapMethod == factory.stringConcatFactoryMembers.makeConcatWithConstants;
-    }
-    return false;
-  }
-
   private static String convertToString(DexValue value, ProgramMethod context) {
     if (value.isDexValueString()) {
       return value.asDexValueString().getValue().toString();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index fb0c4d8..3792bd4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -276,6 +276,8 @@
                       sharedUtilityClassType, dexItemFactory.intArrayType, "$VALUES"))
               .setAccessFlags(FieldAccessFlags.createPublicStaticFinalSynthetic())
               .setApiLevel(appView.computedMinApiLevel())
+              .disableAndroidApiLevelCheckIf(
+                  !appView.options().apiModelingOptions().isApiCallerIdentificationEnabled())
               .build();
       fieldAccessInfoCollectionModifierBuilder
           .recordFieldReadInUnknownContext(valuesField.getReference())
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 cc88ca9..49f2fbb 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AsmUtils;
+import com.android.tools.r8.utils.ClassFileConsumerDataImpl;
 import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer;
@@ -334,7 +335,10 @@
       verifyCf(result);
     }
     ExceptionUtils.withConsumeResourceHandler(
-        options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
+        options.reporter,
+        handler ->
+            consumer.acceptClassFile(
+                new ClassFileConsumerDataImpl(ByteDataView.of(result), desc, handler)));
   }
 
   private int compareTypesThroughLens(DexType a, DexType b) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 383c931..87af2b2 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -375,7 +375,8 @@
                       builder -> {
                         if (!targetDefinition.isAbstract()
                             && targetDefinition.getApiLevelForCode().isNotSetApiLevel()) {
-                          assert target.isLibraryMethod();
+                          assert target.isLibraryMethod()
+                              || !appView.options().apiModelingOptions().enableLibraryApiModeling;
                           builder.setApiLevelForCode(
                               appView
                                   .apiLevelCompute()
diff --git a/src/main/java/com/android/tools/r8/origin/GlobalSyntheticOrigin.java b/src/main/java/com/android/tools/r8/origin/GlobalSyntheticOrigin.java
new file mode 100644
index 0000000..cec0daf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/origin/GlobalSyntheticOrigin.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.origin;
+
+public class GlobalSyntheticOrigin extends Origin {
+
+  private static final Origin INSTANCE = new GlobalSyntheticOrigin(Origin.root());
+
+  public static Origin instance() {
+    return INSTANCE;
+  }
+
+  protected GlobalSyntheticOrigin(Origin parent) {
+    super(parent);
+  }
+
+  @Override
+  public String part() {
+    return "<synthetic>";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java
index bc3cbb5..e09b79c 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileProviderUtils.java
@@ -6,12 +6,23 @@
 
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
+import com.android.tools.r8.TextInputStream;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.UTF8TextInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
 import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
+import java.util.function.Consumer;
 
 public class ArtProfileProviderUtils {
 
@@ -33,4 +44,101 @@
       }
     };
   }
+
+  /** Serialize the given {@param artProfileProvider} to a string for writing it to a dump. */
+  public static String serializeToString(ArtProfileProvider artProfileProvider) throws IOException {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    try (OutputStreamWriter outputStreamWriter =
+        new OutputStreamWriter(baos, StandardCharsets.UTF_8)) {
+      artProfileProvider.getArtProfile(
+          new ArtProfileBuilder() {
+
+            @Override
+            public ArtProfileBuilder addClassRule(
+                Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer) {
+              classRuleBuilderConsumer.accept(
+                  new ArtProfileClassRuleBuilder() {
+
+                    @Override
+                    public ArtProfileClassRuleBuilder setClassReference(
+                        ClassReference classReference) {
+                      writeLine(
+                          outputStreamWriter, ClassReferenceUtils.toSmaliString(classReference));
+                      return this;
+                    }
+                  });
+              return this;
+            }
+
+            @Override
+            public ArtProfileBuilder addMethodRule(
+                Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer) {
+              Box<MethodReference> methodReferenceBox = new Box<>();
+              methodRuleBuilderConsumer.accept(
+                  new ArtProfileMethodRuleBuilder() {
+
+                    @Override
+                    public ArtProfileMethodRuleBuilder setMethodReference(
+                        MethodReference methodReference) {
+                      methodReferenceBox.set(methodReference);
+                      return this;
+                    }
+
+                    @Override
+                    public ArtProfileMethodRuleBuilder setMethodRuleInfo(
+                        Consumer<ArtProfileMethodRuleInfoBuilder> methodRuleInfoBuilderConsumer) {
+                      ArtProfileMethodRuleInfoImpl.Builder artProfileMethodRuleInfoBuilder =
+                          ArtProfileMethodRuleInfoImpl.builder();
+                      methodRuleInfoBuilderConsumer.accept(artProfileMethodRuleInfoBuilder);
+                      ArtProfileMethodRuleInfoImpl artProfileMethodRuleInfo =
+                          artProfileMethodRuleInfoBuilder.build();
+                      try {
+                        artProfileMethodRuleInfo.writeHumanReadableFlags(outputStreamWriter);
+                      } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                      }
+                      return this;
+                    }
+                  });
+              writeLine(
+                  outputStreamWriter, MethodReferenceUtils.toSmaliString(methodReferenceBox.get()));
+              return this;
+            }
+
+            @Override
+            public ArtProfileBuilder addHumanReadableArtProfile(
+                TextInputStream textInputStream,
+                Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
+              try (InputStreamReader inputStreamReader =
+                  new InputStreamReader(
+                      textInputStream.getInputStream(), textInputStream.getCharset())) {
+                char[] buffer = new char[1024];
+                int len = inputStreamReader.read(buffer);
+                while (len != -1) {
+                  outputStreamWriter.write(buffer, 0, len);
+                  len = inputStreamReader.read(buffer);
+                }
+                writeLine(outputStreamWriter);
+              } catch (IOException e) {
+                throw new UncheckedIOException(e);
+              }
+              return this;
+            }
+          });
+    }
+    return baos.toString();
+  }
+
+  private static void writeLine(OutputStreamWriter outputStreamWriter) {
+    writeLine(outputStreamWriter, "");
+  }
+
+  private static void writeLine(OutputStreamWriter outputStreamWriter, String string) {
+    try {
+      outputStreamWriter.write(string);
+      outputStreamWriter.write('\n');
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 7673d69..b46adc0 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -11,13 +11,12 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.KeepInfo.Joiner.asFieldJoinerOrNull;
+import static com.android.tools.r8.utils.CovariantReturnTypeUtils.modelLibraryMethodsWithCovariantReturnTypes;
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static java.util.Collections.emptySet;
 
 import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.androidapi.ComputedApiLevel;
-import com.android.tools.r8.androidapi.CovariantReturnTypeMethods;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -75,7 +74,6 @@
 import com.android.tools.r8.graph.LookupMethodTarget;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.LookupTarget;
-import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
@@ -3586,7 +3584,7 @@
     if (mode.isInitialTreeShaking()) {
       // Amend library methods with covariant return types.
       timing.begin("Model library");
-      modelLibraryMethodsWithCovariantReturnTypes();
+      modelLibraryMethodsWithCovariantReturnTypes(appView);
       timing.end();
     } else if (appView.getKeepInfo() != null) {
       timing.begin("Retain keep info");
@@ -3645,30 +3643,6 @@
             this::recordDependentMinimumKeepInfo);
   }
 
-  private void modelLibraryMethodsWithCovariantReturnTypes() {
-    CovariantReturnTypeMethods.registerMethodsWithCovariantReturnType(
-        appView.dexItemFactory(),
-        method -> {
-          DexLibraryClass libraryClass =
-              DexLibraryClass.asLibraryClassOrNull(
-                  appView.appInfo().definitionForWithoutExistenceAssert(method.getHolderType()));
-          if (libraryClass == null) {
-            return;
-          }
-          // Check if the covariant method exists on the class.
-          DexEncodedMethod covariantMethod = libraryClass.lookupMethod(method);
-          if (covariantMethod != null) {
-            return;
-          }
-          libraryClass.addVirtualMethod(
-              DexEncodedMethod.builder()
-                  .setMethod(method)
-                  .setAccessFlags(MethodAccessFlags.builder().setPublic().build())
-                  .setApiLevelForDefinition(ComputedApiLevel.notSet())
-                  .build());
-        });
-  }
-
   private void applyMinimumKeepInfo(DexProgramClass clazz) {
     EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
     KeepClassInfo.Joiner minimumKeepInfoForClass =
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 8c2a3bf..e92442b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.origin.GlobalSyntheticOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexInfo;
 import java.util.Comparator;
@@ -55,7 +56,7 @@
   static SynthesizingContext fromType(DexType type) {
     // This method should only be used for synthesizing from a non-program context!
     // Thus we have no origin info and place the context in the "base" feature.
-    return new SynthesizingContext(type, type, Origin.unknown(), FeatureSplit.BASE);
+    return new SynthesizingContext(type, type, GlobalSyntheticOrigin.instance(), FeatureSplit.BASE);
   }
 
   static SynthesizingContext fromNonSyntheticInputContext(
@@ -96,6 +97,10 @@
     this.featureSplit = featureSplit;
   }
 
+  public boolean isSyntheticInputClass() {
+    return synthesizingContextType != inputContextType;
+  }
+
   @Override
   public int compareTo(SynthesizingContext other) {
     return Comparator
@@ -103,6 +108,7 @@
         // choose the context prefix for items.
         .comparing(SynthesizingContext::getSynthesizingContextType)
         // To ensure that equals coincides with compareTo == 0, we then compare 'type'.
+        // Also, the input type context is used as the hygienic prefix in intermediate modes.
         .thenComparing(c -> c.inputContextType)
         .compare(this, other);
   }
@@ -111,6 +117,10 @@
     return synthesizingContextType;
   }
 
+  DexType getSynthesizingInputContext(boolean intermediate) {
+    return intermediate ? inputContextType : getSynthesizingContextType();
+  }
+
   Origin getInputContextOrigin() {
     return inputContextOrigin;
   }
@@ -120,14 +130,14 @@
   }
 
   SynthesizingContext rewrite(NonIdentityGraphLens lens) {
-    DexType rewrittenInputeContextType = lens.lookupType(inputContextType);
+    DexType rewrittenInputContextType = lens.lookupType(inputContextType);
     DexType rewrittenSynthesizingContextType = lens.lookupType(synthesizingContextType);
-    return rewrittenInputeContextType == inputContextType
+    return rewrittenInputContextType == inputContextType
             && rewrittenSynthesizingContextType == synthesizingContextType
         ? this
         : new SynthesizingContext(
             rewrittenSynthesizingContextType,
-            rewrittenInputeContextType,
+            rewrittenInputContextType,
             inputContextOrigin,
             featureSplit);
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 097df5d..1a75811 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.synthesis;
 
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -58,7 +59,13 @@
     return context;
   }
 
-  final String getPrefixForExternalSyntheticType() {
+  final String getPrefixForExternalSyntheticType(AppView<?> appView) {
+    if (!appView.options().intermediate && context.isSyntheticInputClass() && !kind.isGlobal()) {
+      // If the input class was a synthetic and the build is non-intermediate, unwind the synthetic
+      // name back to the original context (if present in the textual type).
+      return SyntheticNaming.getOuterContextFromExternalSyntheticType(
+          getKind(), getHolder().getType());
+    }
     return SyntheticNaming.getPrefixForExternalSyntheticType(getKind(), getHolder().getType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 8cf1e77..44f53b1 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -218,7 +218,7 @@
   }
 
   Result computeFinalSynthetics(AppView<?> appView, Timing timing) {
-    assert verifyNoNestedSynthetics(appView.dexItemFactory());
+    assert verifyNoNestedSynthetics(appView);
     assert verifyOneSyntheticPerSyntheticClass();
     DexApplication application;
     Builder lensBuilder = new Builder();
@@ -334,7 +334,7 @@
     return !committed.containsType(type);
   }
 
-  private boolean verifyNoNestedSynthetics(DexItemFactory dexItemFactory) {
+  private boolean verifyNoNestedSynthetics(AppView<?> appView) {
     // Check that the prefix of each synthetic is never itself synthetic.
     committed.forEachItem(
         item -> {
@@ -345,8 +345,12 @@
               SyntheticNaming.getPrefixForExternalSyntheticType(item.getKind(), item.getHolder());
           assert !prefix.contains(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
           DexType context =
-              dexItemFactory.createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
-          assert isNotSyntheticType(context) || synthetics.isGlobalSyntheticClass(context);
+              appView
+                  .dexItemFactory()
+                  .createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
+          assert isNotSyntheticType(context)
+              || item.getContext().isSyntheticInputClass()
+              || synthetics.isGlobalSyntheticClass(context);
         });
     return true;
   }
@@ -599,7 +603,7 @@
             } else {
               groupsPerPrefix
                   .computeIfAbsent(
-                      group.getRepresentative().getPrefixForExternalSyntheticType(),
+                      group.getRepresentative().getPrefixForExternalSyntheticType(appView),
                       k -> new ArrayList<>())
                   .add(group);
             }
@@ -615,7 +619,7 @@
             EquivalenceGroup<T> group = groups.get(i);
             assert group
                 .getRepresentative()
-                .getPrefixForExternalSyntheticType()
+                .getPrefixForExternalSyntheticType(appView)
                 .equals(externalSyntheticTypePrefix);
             // Two equivalence groups in same context type must be distinct otherwise the assignment
             // of the synthetic name will be non-deterministic between the two.
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 631231c..49746f4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -37,8 +37,10 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
@@ -549,6 +551,33 @@
     return syntheticContextsToSyntheticClasses;
   }
 
+  public Collection<Origin> getSynthesizingOrigin(DexType type) {
+    if (!isSynthetic(type)) {
+      return Collections.emptyList();
+    }
+    ImmutableList.Builder<Origin> builder = ImmutableList.builder();
+    forEachSynthesizingContext(
+        type,
+        context -> {
+          builder.add(context.getInputContextOrigin());
+        });
+    return builder.build();
+  }
+
+  public DexType getSynthesizingInputContext(DexType syntheticType, InternalOptions options) {
+    if (!isSynthetic(syntheticType)) {
+      return null;
+    }
+    Box<DexType> uniqueInputContext = new Box<>(null);
+    forEachSynthesizingContext(
+        syntheticType,
+        context -> {
+          assert uniqueInputContext.get() == null;
+          uniqueInputContext.set(context.getSynthesizingInputContext(options.intermediate));
+        });
+    return uniqueInputContext.get();
+  }
+
   public interface SynthesizingContextOracle {
 
     Set<DexReference> getSynthesizingContexts(DexProgramClass clazz);
@@ -729,9 +758,7 @@
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = getSynthesizingContext(context.getClassContext(), appView);
     Function<SynthesizingContext, DexType> contextToType =
-        c ->
-            SyntheticNaming.createInternalType(
-                kind, c, context.getSyntheticSuffix(), appView.dexItemFactory());
+        c -> SyntheticNaming.createInternalType(kind, c, context.getSyntheticSuffix(), appView);
     return internalCreateProgramClass(
         kind, fn, outerContext, contextToType.apply(outerContext), contextToType, appView);
   }
@@ -1028,8 +1055,7 @@
     SynthesizingContext outerContext = getSynthesizingContext(context, appView);
     SyntheticKind kind = kindSelector.select(naming);
     DexType type =
-        SyntheticNaming.createInternalType(
-            kind, outerContext, syntheticIdSupplier.get(), appView.dexItemFactory());
+        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/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index aebc9ae..cf548f9 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -193,7 +193,7 @@
     if (kind.isGlobal()) {
       return type;
     }
-    String prefix = SyntheticNaming.getPrefixForExternalSyntheticType(kind, type);
+    String prefix = SyntheticNaming.getOuterContextFromExternalSyntheticType(kind, type);
     return factory.createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 5c81e4d..95105f8 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
@@ -419,6 +420,19 @@
     return binaryName.substring(0, index);
   }
 
+  static String getOuterContextFromExternalSyntheticType(SyntheticKind kind, DexType type) {
+    assert !kind.isGlobal();
+    String binaryName = type.toBinaryName();
+    int index =
+        binaryName.indexOf(
+            kind.isFixedSuffixSynthetic() ? kind.descriptor : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+    if (index < 0) {
+      throw new Unreachable(
+          "Unexpected failure to determine the context of synthetic class: " + binaryName);
+    }
+    return binaryName.substring(0, index);
+  }
+
   static DexType createFixedType(
       SyntheticKind kind, SynthesizingContext context, DexItemFactory factory) {
     assert kind.isFixedSuffixSynthetic();
@@ -426,14 +440,14 @@
   }
 
   static DexType createInternalType(
-      SyntheticKind kind, SynthesizingContext context, String id, DexItemFactory factory) {
+      SyntheticKind kind, SynthesizingContext context, String id, AppView<?> appView) {
     assert !kind.isFixedSuffixSynthetic();
     return createType(
         INTERNAL_SYNTHETIC_CLASS_SEPARATOR,
         kind,
-        context.getSynthesizingContextType(),
+        context.getSynthesizingInputContext(appView.options().intermediate),
         id,
-        factory);
+        appView.dexItemFactory());
   }
 
   static DexType createExternalType(
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
index 416a9a4..e1000f6 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.tracereferences;
 
+import static com.android.tools.r8.utils.CovariantReturnTypeUtils.modelLibraryMethodsWithCovariantReturnTypes;
+
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.ProgramResource;
@@ -11,8 +13,14 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -73,7 +81,20 @@
     for (ProgramResourceProvider provider : command.getSource()) {
       forEachDescriptor(provider, targetDescriptors::remove);
     }
-    Tracer tracer = new Tracer(targetDescriptors, builder.build(), command.getReporter(), options);
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForTracer(
+            AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
+                new ApplicationReader(builder.build(), options, Timing.empty()).read().toDirect(),
+                ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+                MainDexInfo.none(),
+                GlobalSyntheticsStrategy.forSingleOutputMode(),
+                StartupOrder.empty()));
+    modelLibraryMethodsWithCovariantReturnTypes(appView);
+    Tracer tracer =
+        new Tracer(
+            appView,
+            command.getReporter(),
+            type -> targetDescriptors.contains(type.toDescriptorString()));
     tracer.run(command.getConsumer());
   }
 
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 97c4499..f182a0b 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -4,11 +4,8 @@
 package com.android.tools.r8.tracereferences;
 
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.diagnostic.DefinitionContext;
 import com.android.tools.r8.diagnostic.internal.DefinitionContextUtils;
-import com.android.tools.r8.experimental.startup.StartupOrder;
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassResolutionResult;
@@ -41,17 +38,11 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.tracereferences.TraceReferencesConsumer.TracedReference;
 import com.android.tools.r8.tracereferences.internal.TracedClassImpl;
 import com.android.tools.r8.tracereferences.internal.TracedFieldImpl;
 import com.android.tools.r8.tracereferences.internal.TracedMethodImpl;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BooleanBox;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
-import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Function;
@@ -63,24 +54,6 @@
   private final DiagnosticsHandler diagnostics;
   private final Predicate<DexType> targetPredicate;
 
-  Tracer(
-      Set<String> targetDescriptors,
-      AndroidApp inputApp,
-      DiagnosticsHandler diagnostics,
-      InternalOptions options)
-      throws IOException {
-    this(
-        AppView.createForTracer(
-            AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
-                new ApplicationReader(inputApp, options, Timing.empty()).read().toDirect(),
-                ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-                MainDexInfo.none(),
-                GlobalSyntheticsStrategy.forSingleOutputMode(),
-                StartupOrder.empty())),
-        diagnostics,
-        type -> targetDescriptors.contains(type.toDescriptorString()));
-  }
-
   public Tracer(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       DiagnosticsHandler diagnostics,
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 2e7e2e3..fd21f2a 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -47,12 +47,13 @@
   MASTER(35), // API level for master is tentative.
   ANDROID_PLATFORM(10000);
 
-  // When updating LATEST and a new version goes stable, add a new api-versions.xml to third_party
-  // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest.
-  public static final AndroidApiLevel LATEST = T;
+  // 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
+  // with that update third_party/android_jar/libcore_latest/core-oj.jar and run
+  // GenerateCovariantReturnTypeMethodsTest.
+  public static final AndroidApiLevel LATEST = U;
 
-  // TODO(b/268601605): When adding U to the test matrix, set this to LATEST.
-  public static final AndroidApiLevel API_DATABASE_LEVEL = U;
+  public static final AndroidApiLevel API_DATABASE_LEVEL = LATEST;
 
   private final int level;
 
@@ -109,7 +110,7 @@
 
   public static AndroidApiLevel getAndroidApiLevel(int apiLevel) {
     assert apiLevel > 0;
-    assert T == LATEST; // This has to be updated when we add new api levels.
+    assert U == LATEST; // This has to be updated when we add new api levels.
     assert ANDROID_PLATFORM.isGreaterThan(LATEST);
     switch (apiLevel) {
       case 1:
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 8ea5c72..cf3047d 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -42,6 +42,8 @@
 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.profile.art.ArtProfileProvider;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.synthesis.SyntheticItems;
@@ -539,6 +541,9 @@
             StringUtils.joinLines(dumpOptions.getMainDexKeepRules()).getBytes(),
             ZipEntry.DEFLATED);
       }
+      if (dumpOptions.hasArtProfileProviders()) {
+        dumpArtProfileProviders(dumpOptions.getArtProfileProviders(), options, out);
+      }
       if (dumpOptions.hasStartupProfileProviders()) {
         dumpStartupProfileProviders(dumpOptions.getStartupProfileProviders(), options, out);
       }
@@ -571,6 +576,23 @@
     return nextDexIndex;
   }
 
+  private void dumpArtProfileProviders(
+      Collection<ArtProfileProvider> artProfileProviders,
+      InternalOptions options,
+      ZipOutputStream out)
+      throws IOException {
+    int artProfileProviderIndex = 1;
+    for (ArtProfileProvider artProfileProvider : artProfileProviders) {
+      String artProfileFileName = "art-profile-" + artProfileProviderIndex + ".txt";
+      writeToZipStream(
+          out,
+          artProfileFileName,
+          ArtProfileProviderUtils.serializeToString(artProfileProvider).getBytes(),
+          ZipEntry.DEFLATED);
+      artProfileProviderIndex++;
+    }
+  }
+
   private void dumpStartupProfileProviders(
       Collection<StartupProfileProvider> startupProfileProviders,
       InternalOptions options,
@@ -578,11 +600,10 @@
       throws IOException {
     int startupProfileProviderIndex = 1;
     for (StartupProfileProvider startupProfileProvider : startupProfileProviders) {
-      String startupProfileProviderFileName =
-          "startup-profile-" + startupProfileProviderIndex + ".txt";
+      String startupProfileFileName = "startup-profile-" + startupProfileProviderIndex + ".txt";
       writeToZipStream(
           out,
-          startupProfileProviderFileName,
+          startupProfileFileName,
           StartupProfileProviderUtils.serializeToString(options, startupProfileProvider).getBytes(),
           ZipEntry.DEFLATED);
       startupProfileProviderIndex++;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
index 7a77e25..1c01a7b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppConsumers.java
@@ -4,14 +4,16 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.BaseCompilerCommand;
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumerData;
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumerData;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DexIndexedConsumer.ForwardingConsumer;
+import com.android.tools.r8.DexIndexedConsumerData;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.ResourceException;
@@ -103,13 +105,9 @@
               new Int2ReferenceAVLTreeMap<>();
 
           @Override
-          public void accept(
-              int fileIndex,
-              ByteDataView data,
-              Set<String> descriptors,
-              DiagnosticsHandler handler) {
-            super.accept(fileIndex, data, descriptors, handler);
-            addDexFile(fileIndex, data.copyByteData(), descriptors);
+          public void acceptDexIndexedFile(DexIndexedConsumerData data) {
+            super.acceptDexIndexedFile(data);
+            addDexFile(data.getFileIndex(), data.getByteDataCopy(), data.getClassDescriptors());
           }
 
           @Override
@@ -182,13 +180,12 @@
           private TreeMap<String, DescriptorsWithContents> files = new TreeMap<>();
 
           @Override
-          public void accept(
-              String primaryClassDescriptor,
-              ByteDataView data,
-              Set<String> descriptors,
-              DiagnosticsHandler handler) {
-            super.accept(primaryClassDescriptor, data, descriptors, handler);
-            addDexFile(primaryClassDescriptor, data.copyByteData(), descriptors);
+          public void acceptDexFile(DexFilePerClassFileConsumerData data) {
+            super.acceptDexFile(data);
+            addDexFile(
+                data.getPrimaryClassDescriptor(),
+                data.getByteDataCopy(),
+                data.getClassDescriptors());
           }
 
           synchronized void addDexFile(
@@ -258,9 +255,9 @@
           private List<DescriptorsWithContents> files = new ArrayList<>();
 
           @Override
-          public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-            super.accept(data, descriptor, handler);
-            addClassFile(data.copyByteData(), descriptor);
+          public void acceptClassFile(ClassFileConsumerData data) {
+            super.acceptClassFile(data);
+            addClassFile(data.getByteDataCopy(), data.getClassDescriptor());
           }
 
           synchronized void addClassFile(byte[] data, String descriptor) {
diff --git a/src/main/java/com/android/tools/r8/utils/ClassFileConsumerDataImpl.java b/src/main/java/com/android/tools/r8/utils/ClassFileConsumerDataImpl.java
new file mode 100644
index 0000000..b375086
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ClassFileConsumerDataImpl.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumerData;
+import com.android.tools.r8.DiagnosticsHandler;
+
+/** Internal implementation of the consumer data. */
+public class ClassFileConsumerDataImpl implements ClassFileConsumerData {
+
+  private final ByteDataView data;
+  private final String descriptor;
+  private final DiagnosticsHandler handler;
+
+  public ClassFileConsumerDataImpl(
+      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+    this.data = data;
+    this.descriptor = descriptor;
+    this.handler = handler;
+  }
+
+  @Override
+  public ByteDataView getByteDataView() {
+    return data;
+  }
+
+  @Override
+  public byte[] getByteDataCopy() {
+    return data.copyByteData();
+  }
+
+  @Override
+  public String getClassDescriptor() {
+    return descriptor;
+  }
+
+  @Override
+  public DiagnosticsHandler getDiagnosticsHandler() {
+    return handler;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java
new file mode 100644
index 0000000..2a928b0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class CompileDumpBase {
+
+  static void setEnableExperimentalMissingLibraryApiModeling(
+      Object builder, boolean enableMissingLibraryApiModeling) {
+    getReflectiveBuilderMethod(
+            builder, "setEnableExperimentalMissingLibraryApiModeling", boolean.class)
+        .accept(new Object[] {enableMissingLibraryApiModeling});
+  }
+
+  static void setAndroidPlatformBuild(Object builder, boolean androidPlatformBuild) {
+    getReflectiveBuilderMethod(builder, "setAndroidPlatformBuild", boolean.class)
+        .accept(new Object[] {androidPlatformBuild});
+  }
+
+  static void addArtProfilesForRewriting(Object builder, Map<Path, Path> artProfileFiles) {
+    try {
+      Class<?> artProfileProviderClass =
+          Class.forName("com.android.tools.r8.profile.art.ArtProfileProvider");
+      Class<?> artProfileConsumerClass =
+          Class.forName("com.android.tools.r8.profile.art.ArtProfileConsumer");
+      artProfileFiles.forEach(
+          (artProfile, residualArtProfile) ->
+              getReflectiveBuilderMethod(
+                      builder,
+                      "addArtProfileForRewriting",
+                      artProfileProviderClass,
+                      artProfileConsumerClass)
+                  .accept(
+                      new Object[] {
+                        createArtProfileProvider(artProfile),
+                        createResidualArtProfileConsumer(residualArtProfile)
+                      }));
+    } catch (ClassNotFoundException e) {
+      // Ignore.
+    }
+  }
+
+  static void addStartupProfileProviders(Object builder, List<Path> startupProfileFiles) {
+    getReflectiveBuilderMethod(builder, "addStartupProfileProviders", Collection.class)
+        .accept(new Object[] {createStartupProfileProviders(startupProfileFiles)});
+  }
+
+  static Object createArtProfileProvider(Path artProfile) {
+    Object[] artProfileProvider = new Object[1];
+    boolean found =
+        callReflectiveUtilsMethod(
+            "createArtProfileProviderFromDumpFile",
+            new Class<?>[] {Path.class},
+            fn -> artProfileProvider[0] = fn.apply(new Object[] {artProfile}));
+    if (!found) {
+      System.out.println(
+          "Unable to add art profiles as input. "
+              + "Method createArtProfileProviderFromDumpFile() was not found.");
+      return null;
+    }
+    System.out.println(artProfileProvider[0]);
+    return artProfileProvider[0];
+  }
+
+  static Object createResidualArtProfileConsumer(Path residualArtProfile) {
+    Object[] residualArtProfileConsumer = new Object[1];
+    boolean found =
+        callReflectiveUtilsMethod(
+            "createResidualArtProfileConsumerFromDumpFile",
+            new Class<?>[] {Path.class},
+            fn -> residualArtProfileConsumer[0] = fn.apply(new Object[] {residualArtProfile}));
+    if (!found) {
+      System.out.println(
+          "Unable to add art profiles as input. "
+              + "Method createResidualArtProfileConsumerFromDumpFile() was not found.");
+      return null;
+    }
+    System.out.println(residualArtProfileConsumer[0]);
+    return residualArtProfileConsumer[0];
+  }
+
+  static Collection<Object> createStartupProfileProviders(List<Path> startupProfileFiles) {
+    List<Object> startupProfileProviders = new ArrayList<>();
+    for (Path startupProfileFile : startupProfileFiles) {
+      boolean found =
+          callReflectiveUtilsMethod(
+              "createStartupProfileProviderFromDumpFile",
+              new Class<?>[] {Path.class},
+              fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile})));
+      if (!found) {
+        System.out.println(
+            "Unable to add startup profiles as input. "
+                + "Method createStartupProfileProviderFromDumpFile() was not found.");
+        break;
+      }
+    }
+    return startupProfileProviders;
+  }
+
+  static Consumer<Object[]> getReflectiveBuilderMethod(
+      Object builder, String setter, Class<?>... parameters) {
+    try {
+      Method declaredMethod = CompatProguardCommandBuilder.class.getMethod(setter, parameters);
+      return args -> {
+        try {
+          declaredMethod.invoke(builder, args);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      };
+    } catch (NoSuchMethodException e) {
+      System.out.println(setter + " is not available on the compiledump version.");
+      // The option is not available so we just return an empty consumer
+      return args -> {};
+    }
+  }
+
+  static boolean callReflectiveUtilsMethod(
+      String methodName, Class<?>[] parameters, Consumer<Function<Object[], Object>> fnConsumer) {
+    Class<?> utilsClass;
+    try {
+      utilsClass = Class.forName("com.android.tools.r8.utils.CompileDumpUtils");
+    } catch (ClassNotFoundException e) {
+      return false;
+    }
+
+    Method declaredMethod;
+    try {
+      declaredMethod = utilsClass.getDeclaredMethod(methodName, parameters);
+    } catch (NoSuchMethodException e) {
+      return false;
+    }
+
+    fnConsumer.accept(
+        args -> {
+          try {
+            return declaredMethod.invoke(null, args);
+          } catch (Exception e) {
+            throw new RuntimeException(e);
+          }
+        });
+    return true;
+  }
+
+  // We cannot use StringResource since this class is added to the class path and has access only
+  // to the public APIs.
+  static String readAllBytesJava7(Path filePath) {
+    String content = "";
+
+    try {
+      content = new String(Files.readAllBytes(filePath));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    return content;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 57cdc5f..c4612a7 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -12,21 +12,15 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.StringConsumer;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
@@ -38,7 +32,7 @@
  * valid on past version of the R8 API. Thus there is little else to do than reimplement the parts
  * we want to support for reading dumps.
  */
-public class CompileDumpCompatR8 {
+public class CompileDumpCompatR8 extends CompileDumpBase {
 
   private static final List<String> VALID_OPTIONS =
       Arrays.asList(
@@ -66,7 +60,7 @@
           "--startup-profile");
 
   private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
-      Arrays.asList("--feature-jar");
+      Arrays.asList("--art-profile", "--feature-jar");
 
   private static boolean FileUtils_isArchive(Path path) {
     String name = path.getFileName().toString().toLowerCase();
@@ -90,6 +84,7 @@
     List<Path> classpath = new ArrayList<>();
     List<Path> config = new ArrayList<>();
     List<Path> mainDexRulesFiles = new ArrayList<>();
+    Map<Path, Path> artProfileFiles = new LinkedHashMap<>();
     List<Path> startupProfileFiles = new ArrayList<>();
     int minApi = 1;
     int threads = -1;
@@ -193,6 +188,11 @@
         String firstOperand = args[++i];
         String secondOperand = args[++i];
         switch (option) {
+          case "--art-profile":
+            {
+              artProfileFiles.put(Paths.get(firstOperand), Paths.get(secondOperand));
+              break;
+            }
           case "--feature-jar":
             {
               Path featureIn = Paths.get(firstOperand);
@@ -220,13 +220,10 @@
             .addMainDexRulesFiles(mainDexRulesFiles)
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode);
-    getReflectiveBuilderMethod(
-            commandBuilder, "setEnableExperimentalMissingLibraryApiModeling", boolean.class)
-        .accept(new Object[] {enableMissingLibraryApiModeling});
-    getReflectiveBuilderMethod(commandBuilder, "setAndroidPlatformBuild", boolean.class)
-        .accept(new Object[] {androidPlatformBuild});
-    getReflectiveBuilderMethod(commandBuilder, "addStartupProfileProviders", Collection.class)
-        .accept(new Object[] {createStartupProfileProviders(startupProfileFiles)});
+    addArtProfilesForRewriting(commandBuilder, artProfileFiles);
+    addStartupProfileProviders(commandBuilder, startupProfileFiles);
+    setAndroidPlatformBuild(commandBuilder, androidPlatformBuild);
+    setEnableExperimentalMissingLibraryApiModeling(commandBuilder, enableMissingLibraryApiModeling);
     if (desugaredLibJson != null) {
       commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
     }
@@ -259,81 +256,4 @@
       R8.run(command);
     }
   }
-
-  private static Collection<Object> createStartupProfileProviders(List<Path> startupProfileFiles) {
-    List<Object> startupProfileProviders = new ArrayList<>();
-    for (Path startupProfileFile : startupProfileFiles) {
-      boolean found =
-          callReflectiveUtilsMethod(
-              "createStartupProfileProviderFromDumpFile",
-              new Class<?>[] {Path.class},
-              fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile})));
-      if (!found) {
-        System.out.println(
-            "Unable to add startup profiles as input. "
-                + "Method createStartupProfileProviderFromDumpFile() was not found.");
-        break;
-      }
-    }
-    return startupProfileProviders;
-  }
-
-  private static Consumer<Object[]> getReflectiveBuilderMethod(
-      Builder builder, String setter, Class<?>... parameters) {
-    try {
-      Method declaredMethod = CompatProguardCommandBuilder.class.getMethod(setter, parameters);
-      return args -> {
-        try {
-          declaredMethod.invoke(builder, args);
-        } catch (Exception e) {
-          throw new RuntimeException(e);
-        }
-      };
-    } catch (NoSuchMethodException e) {
-      System.out.println(setter + " is not available on the compiledump version.");
-      // The option is not available so we just return an empty consumer
-      return args -> {};
-    }
-  }
-
-  private static boolean callReflectiveUtilsMethod(
-      String methodName, Class<?>[] parameters, Consumer<Function<Object[], Object>> fnConsumer) {
-    Class<?> utilsClass;
-    try {
-      utilsClass = Class.forName("com.android.tools.r8.utils.CompileDumpUtils");
-    } catch (ClassNotFoundException e) {
-      return false;
-    }
-
-    Method declaredMethod;
-    try {
-      declaredMethod = utilsClass.getMethod(methodName, parameters);
-    } catch (NoSuchMethodException e) {
-      return false;
-    }
-
-    fnConsumer.accept(
-        args -> {
-          try {
-            return declaredMethod.invoke(null, args);
-          } catch (Exception e) {
-            throw new RuntimeException(e);
-          }
-        });
-    return true;
-  }
-
-  // We cannot use StringResource since this class is added to the class path and has access only
-  // to the public APIs.
-  private static String readAllBytesJava7(Path filePath) {
-    String content = "";
-
-    try {
-      content = new String(Files.readAllBytes(filePath));
-    } catch (IOException e) {
-      e.printStackTrace();
-    }
-
-    return content;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
index 613701c..b63f5d8 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpD8.java
@@ -8,19 +8,15 @@
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.OutputMode;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Wrapper to make it easy to call D8 mode when compiling a dump file.
@@ -32,7 +28,7 @@
  * valid on past version of the D8 API. Thus there is little else to do than reimplement the parts
  * we want to support for reading dumps.
  */
-public class CompileDumpD8 {
+public class CompileDumpD8 extends CompileDumpBase {
 
   private static final List<String> VALID_OPTIONS =
       Arrays.asList(
@@ -55,6 +51,9 @@
           "--threads",
           "--startup-profile");
 
+  private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
+      Arrays.asList("--art-profile");
+
   public static void main(String[] args) throws CompilationFailedException {
     OutputMode outputMode = OutputMode.DexIndexed;
     Path outputPath = null;
@@ -64,6 +63,7 @@
     List<Path> library = new ArrayList<>();
     List<Path> classpath = new ArrayList<>();
     List<Path> mainDexRulesFiles = new ArrayList<>();
+    Map<Path, Path> artProfileFiles = new LinkedHashMap<>();
     List<Path> startupProfileFiles = new ArrayList<>();
     int minApi = 1;
     int threads = -1;
@@ -143,6 +143,18 @@
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
+      } else if (VALID_OPTIONS_WITH_TWO_OPERANDS.contains(option)) {
+        String firstOperand = args[++i];
+        String secondOperand = args[++i];
+        switch (option) {
+          case "--art-profile":
+            {
+              artProfileFiles.put(Paths.get(firstOperand), Paths.get(secondOperand));
+              break;
+            }
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
       } else {
         program.add(Paths.get(option));
       }
@@ -155,13 +167,10 @@
             .addMainDexRulesFiles(mainDexRulesFiles)
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode);
-    getReflectiveBuilderMethod(
-            commandBuilder, "setEnableExperimentalMissingLibraryApiModeling", boolean.class)
-        .accept(new Object[] {enableMissingLibraryApiModeling});
-    getReflectiveBuilderMethod(commandBuilder, "setAndroidPlatformBuild", boolean.class)
-        .accept(new Object[] {androidPlatformBuild});
-    getReflectiveBuilderMethod(commandBuilder, "addStartupProfileProviders", Collection.class)
-        .accept(new Object[] {createStartupProfileProviders(startupProfileFiles)});
+    addArtProfilesForRewriting(commandBuilder, artProfileFiles);
+    addStartupProfileProviders(commandBuilder, startupProfileFiles);
+    setAndroidPlatformBuild(commandBuilder, androidPlatformBuild);
+    setEnableExperimentalMissingLibraryApiModeling(commandBuilder, enableMissingLibraryApiModeling);
     if (desugaredLibJson != null) {
       commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
     }
@@ -178,81 +187,4 @@
       D8.run(command);
     }
   }
-
-  private static Collection<Object> createStartupProfileProviders(List<Path> startupProfileFiles) {
-    List<Object> startupProfileProviders = new ArrayList<>();
-    for (Path startupProfileFile : startupProfileFiles) {
-      boolean found =
-          callReflectiveUtilsMethod(
-              "createStartupProfileProviderFromDumpFile",
-              new Class<?>[] {Path.class},
-              fn -> startupProfileProviders.add(fn.apply(new Object[] {startupProfileFile})));
-      if (!found) {
-        System.out.println(
-            "Unable to add startup profiles as input. "
-                + "Method createStartupProfileProviderFromDumpFile() was not found.");
-        break;
-      }
-    }
-    return startupProfileProviders;
-  }
-
-  private static Consumer<Object[]> getReflectiveBuilderMethod(
-      D8Command.Builder builder, String setter, Class<?>... parameters) {
-    try {
-      Method declaredMethod = D8Command.Builder.class.getMethod(setter, parameters);
-      return args -> {
-        try {
-          declaredMethod.invoke(builder, args);
-        } catch (Exception e) {
-          throw new RuntimeException(e);
-        }
-      };
-    } catch (NoSuchMethodException e) {
-      e.printStackTrace();
-      // The option is not available so we just return an empty consumer
-      return args -> {};
-    }
-  }
-
-  private static boolean callReflectiveUtilsMethod(
-      String methodName, Class<?>[] parameters, Consumer<Function<Object[], Object>> fnConsumer) {
-    Class<?> utilsClass;
-    try {
-      utilsClass = Class.forName("com.android.tools.r8.utils.CompileDumpUtils");
-    } catch (ClassNotFoundException e) {
-      return false;
-    }
-
-    Method declaredMethod;
-    try {
-      declaredMethod = utilsClass.getMethod(methodName, parameters);
-    } catch (NoSuchMethodException e) {
-      return false;
-    }
-
-    fnConsumer.accept(
-        args -> {
-          try {
-            return declaredMethod.invoke(null, args);
-          } catch (Exception e) {
-            throw new RuntimeException(e);
-          }
-        });
-    return true;
-  }
-
-  // We cannot use StringResource since this class is added to the class path and has access only
-  // to the public APIs.
-  private static String readAllBytesJava7(Path filePath) {
-    String content = "";
-
-    try {
-      content = new String(Files.readAllBytes(filePath));
-    } catch (IOException e) {
-      e.printStackTrace();
-    }
-
-    return content;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java b/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java
index 14acc11..f0275f5 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpUtils.java
@@ -7,6 +7,10 @@
 import com.android.tools.r8.KeepMethodForCompileDump;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.profile.art.ArtProfileConsumer;
+import com.android.tools.r8.profile.art.ArtProfileConsumerUtils;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.startup.StartupProfileBuilder;
@@ -20,6 +24,16 @@
 class CompileDumpUtils {
 
   @KeepMethodForCompileDump
+  static ArtProfileProvider createArtProfileProviderFromDumpFile(Path artProfile) {
+    return ArtProfileProviderUtils.createFromHumanReadableArtProfile(artProfile);
+  }
+
+  @KeepMethodForCompileDump
+  static ArtProfileConsumer createResidualArtProfileConsumerFromDumpFile(Path residualArtProfile) {
+    return ArtProfileConsumerUtils.create(residualArtProfile);
+  }
+
+  @KeepMethodForCompileDump
   static StartupProfileProvider createStartupProfileProviderFromDumpFile(Path path) {
     return new StartupProfileProvider() {
 
diff --git a/src/main/java/com/android/tools/r8/utils/CovariantReturnTypeUtils.java b/src/main/java/com/android/tools/r8/utils/CovariantReturnTypeUtils.java
new file mode 100644
index 0000000..202132f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CovariantReturnTypeUtils.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.androidapi.CovariantReturnTypeMethods;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.MethodAccessFlags;
+
+public class CovariantReturnTypeUtils {
+
+  public static void modelLibraryMethodsWithCovariantReturnTypes(AppView<?> appView) {
+    CovariantReturnTypeMethods.registerMethodsWithCovariantReturnType(
+        appView.dexItemFactory(),
+        method -> {
+          DexLibraryClass libraryClass =
+              DexLibraryClass.asLibraryClassOrNull(
+                  appView.appInfo().definitionForWithoutExistenceAssert(method.getHolderType()));
+          if (libraryClass == null) {
+            return;
+          }
+          // Check if the covariant method exists on the class.
+          DexEncodedMethod covariantMethod = libraryClass.lookupMethod(method);
+          if (covariantMethod != null) {
+            return;
+          }
+          libraryClass.addVirtualMethod(
+              DexEncodedMethod.builder()
+                  .setMethod(method)
+                  .setAccessFlags(MethodAccessFlags.builder().setPublic().build())
+                  .setApiLevelForDefinition(ComputedApiLevel.notSet())
+                  .build());
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DexFilePerClassFileConsumerDataImpl.java b/src/main/java/com/android/tools/r8/utils/DexFilePerClassFileConsumerDataImpl.java
new file mode 100644
index 0000000..9dc9e8b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DexFilePerClassFileConsumerDataImpl.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DexFilePerClassFileConsumerData;
+import com.android.tools.r8.DiagnosticsHandler;
+import java.util.Set;
+
+public class DexFilePerClassFileConsumerDataImpl implements DexFilePerClassFileConsumerData {
+
+  private final String primaryClassDescriptor;
+  private final String synthesizingContextDescriptor;
+  private final ByteDataView data;
+  private final Set<String> classDescriptors;
+  private final DiagnosticsHandler handler;
+
+  public DexFilePerClassFileConsumerDataImpl(
+      String primaryClassDescriptor,
+      String synthesizingContextDescriptor,
+      ByteDataView data,
+      Set<String> classDescriptors,
+      DiagnosticsHandler handler) {
+    this.primaryClassDescriptor = primaryClassDescriptor;
+    this.synthesizingContextDescriptor = synthesizingContextDescriptor;
+    this.data = data;
+    this.classDescriptors = classDescriptors;
+    this.handler = handler;
+  }
+
+  @Override
+  public String getPrimaryClassDescriptor() {
+    return primaryClassDescriptor;
+  }
+
+  @Override
+  public String getSynthesizingContextForPrimaryClass() {
+    return synthesizingContextDescriptor;
+  }
+
+  @Override
+  public ByteDataView getByteDataView() {
+    return data;
+  }
+
+  @Override
+  public byte[] getByteDataCopy() {
+    return data.copyByteData();
+  }
+
+  @Override
+  public Set<String> getClassDescriptors() {
+    return classDescriptors;
+  }
+
+  @Override
+  public DiagnosticsHandler getDiagnosticsHandler() {
+    return handler;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DexIndexedConsumerDataImpl.java b/src/main/java/com/android/tools/r8/utils/DexIndexedConsumerDataImpl.java
new file mode 100644
index 0000000..2de21e4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DexIndexedConsumerDataImpl.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DexIndexedConsumerData;
+import com.android.tools.r8.DiagnosticsHandler;
+import java.util.Set;
+
+public class DexIndexedConsumerDataImpl implements DexIndexedConsumerData {
+
+  private final int fileIndex;
+  private final ByteDataView data;
+  private final Set<String> classDescriptors;
+  private final DiagnosticsHandler handler;
+
+  public DexIndexedConsumerDataImpl(
+      int fileIndex, ByteDataView data, Set<String> classDescriptors, DiagnosticsHandler handler) {
+    this.fileIndex = fileIndex;
+    this.data = data;
+    this.classDescriptors = classDescriptors;
+    this.handler = handler;
+  }
+
+  @Override
+  public int getFileIndex() {
+    return fileIndex;
+  }
+
+  @Override
+  public ByteDataView getByteDataView() {
+    return data;
+  }
+
+  @Override
+  public byte[] getByteDataCopy() {
+    return data.copyByteData();
+  }
+
+  @Override
+  public Set<String> getClassDescriptors() {
+    return classDescriptors;
+  }
+
+  @Override
+  public DiagnosticsHandler getDiagnosticsHandler() {
+    return handler;
+  }
+}
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..6fcc519 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumerData;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.GlobalSyntheticsConsumer;
 import com.android.tools.r8.ProgramConsumer;
@@ -102,12 +103,8 @@
     }
 
     @Override
-    public synchronized void accept(
-        String primaryClassDescriptor,
-        ByteDataView data,
-        Set<String> descriptors,
-        DiagnosticsHandler handler) {
-      builder.addGlobalSynthetic(primaryClassDescriptor, data.copyByteData());
+    public synchronized void acceptDexFile(DexFilePerClassFileConsumerData data) {
+      builder.addGlobalSynthetic(data.getPrimaryClassDescriptor(), data.getByteDataCopy());
     }
 
     @Override
@@ -149,12 +146,8 @@
     }
 
     @Override
-    public void accept(
-        String primaryClassDescriptor,
-        ByteDataView data,
-        Set<String> descriptors,
-        DiagnosticsHandler handler) {
-      addGlobal(primaryClassDescriptor, data);
+    public void acceptDexFile(DexFilePerClassFileConsumerData data) {
+      addGlobal(data.getPrimaryClassDescriptor(), data.getByteDataView());
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f7c00ee..b26527a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2850,14 +2850,15 @@
   // the symbolic reference java.lang.Object.
   //
   // The support was added in Android O, however at least for j.l.CharSequence.equals the handling
-  // in Art was incorrect (b/231450655).
+  // in Art was incorrect (b/231450655). Further down the line b/271408544 indicate that it is not
+  // until Android S that this is working.
   //
   // javac started generating code like this with the fix for JDK-8272564, which will be part of
   // JDK 18.
   //
   // See b/218298666.
   public boolean canHaveInvokeInterfaceToObjectMethodBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.P);
+    return canHaveBugPresentUntil(AndroidApiLevel.S);
   }
 
   // Until we fully drop support for API levels < 16, we have to emit an empty annotation set to
diff --git a/src/main/java/com/android/tools/r8/utils/UTF8TextInputStream.java b/src/main/java/com/android/tools/r8/utils/UTF8TextInputStream.java
index 20252e7..709df16 100644
--- a/src/main/java/com/android/tools/r8/utils/UTF8TextInputStream.java
+++ b/src/main/java/com/android/tools/r8/utils/UTF8TextInputStream.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.TextInputStream;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.Charset;
@@ -20,6 +21,10 @@
     this(Files.newInputStream(path));
   }
 
+  public UTF8TextInputStream(String string) {
+    this(new ByteArrayInputStream(string.getBytes()));
+  }
+
   public UTF8TextInputStream(InputStream inputStream) {
     this.inputStream = inputStream;
   }
diff --git a/src/test/examplesJava17/records/RecordInterface.java b/src/test/examplesJava17/records/RecordInterface.java
new file mode 100644
index 0000000..8fc1e56
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordInterface.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package records;
+
+public class RecordInterface {
+
+  interface Human {
+    default void printHuman() {
+      System.out.println("Human");
+    }
+  }
+
+  record Person(String name, int age) implements Human {}
+
+  public static void main(String[] args) {
+    Person janeDoe = new Person("Jane Doe", 42);
+    janeDoe.printHuman();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 11cebfd..ff10302 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -44,7 +44,6 @@
 import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 import java.util.function.Predicate;
 import java.util.zip.ZipFile;
 import org.junit.Test;
@@ -466,15 +465,10 @@
     class MultiTypeConsumer implements DexIndexedConsumer, DexFilePerClassFileConsumer {
 
       @Override
-      public void accept(
-          String primaryClassDescriptor,
-          ByteDataView data,
-          Set<String> descriptors,
-          DiagnosticsHandler handler) {}
+      public void acceptDexFile(DexFilePerClassFileConsumerData data) {}
 
       @Override
-      public void accept(
-          int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {}
+      public void acceptDexIndexedFile(DexIndexedConsumerData data) {}
 
       @Override
       public void finished(DiagnosticsHandler handler) {
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 6ed73d8..e8a4d1d 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import java.nio.file.Paths;
 import java.util.Collection;
-import java.util.Set;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,15 +59,11 @@
             .setProgramConsumer(
                 new DexIndexedConsumer.ForwardingConsumer(null) {
                   @Override
-                  public void accept(
-                      int fileIndex,
-                      ByteDataView data,
-                      Set<String> descriptors,
-                      DiagnosticsHandler handler) {
+                  public void acceptDexIndexedFile(DexIndexedConsumerData data) {
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+                          ExtractMarker.extractMarkerFromDexProgramData(data.getByteDataCopy());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
@@ -104,12 +99,11 @@
             .setProgramConsumer(
                 new ClassFileConsumer.ForwardingConsumer(null) {
                   @Override
-                  public void accept(
-                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                  public void acceptClassFile(ClassFileConsumerData data) {
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+                          ExtractMarker.extractMarkerFromClassProgramData(data.getByteDataCopy());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
index f0b68de..5be0095 100644
--- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -63,15 +63,11 @@
             .setProgramConsumer(
                 new DexIndexedConsumer.ForwardingConsumer(null) {
                   @Override
-                  public void accept(
-                      int fileIndex,
-                      ByteDataView data,
-                      Set<String> descriptors,
-                      DiagnosticsHandler handler) {
+                  public void acceptDexIndexedFile(DexIndexedConsumerData data) {
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+                          ExtractMarker.extractMarkerFromDexProgramData(data.getByteDataCopy());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
@@ -111,12 +107,11 @@
             .setProgramConsumer(
                 new ClassFileConsumer.ForwardingConsumer(null) {
                   @Override
-                  public void accept(
-                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                  public void acceptClassFile(ClassFileConsumerData data) {
                     Marker marker;
                     try {
                       Collection<Marker> markers =
-                          ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+                          ExtractMarker.extractMarkerFromClassProgramData(data.getByteDataCopy());
                       assertEquals(1, markers.size());
                       marker = markers.iterator().next();
                     } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java b/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
deleted file mode 100644
index 90343b7..0000000
--- a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dex.DexParser;
-import com.android.tools.r8.dex.DexSection;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class R8CodeCanonicalizationTest extends TestBase {
-
-  private static final Path SOURCE_DEX = Paths.get(
-      ToolHelper.EXAMPLES_BUILD_DIR, "invokeempty", "classes.dex");
-
-  private int readNumberOfCodes(Path file) throws IOException {
-    DexSection[] dexSections = DexParser.parseMapFrom(file);
-    for (DexSection dexSection : dexSections) {
-      if (dexSection.type == Constants.TYPE_CODE_ITEM) {
-        return dexSection.length;
-      }
-    }
-    return 0;
-  }
-
-  @Test
-  public void testNumberOfCodeItemsUnchanged() throws Exception {
-    int numberOfCodes = readNumberOfCodes(SOURCE_DEX);
-    R8Command.Builder builder = R8Command.builder()
-        .setDisableTreeShaking(true)
-        .setDisableMinification(true)
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-        .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed);
-    ToolHelper.getAppBuilder(builder).addProgramFiles(SOURCE_DEX);
-    R8.run(builder.build());
-
-    int newNumberOfCodes = readNumberOfCodes(
-        Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
-    Assert.assertEquals("Number of codeitems does not change.", numberOfCodes, newNumberOfCodes);
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
index 32f0871..d4ab329 100644
--- a/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/R8ModeMarkerTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.dex.Marker;
 import java.util.Collection;
-import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -40,11 +39,10 @@
     }
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
       try {
         Collection<Marker> markers =
-            ExtractMarker.extractMarkerFromDexProgramData(data.copyByteData());
+            ExtractMarker.extractMarkerFromDexProgramData(data.getByteDataCopy());
         assertEquals(1, markers.size());
         marker = markers.iterator().next();
       } catch (Exception e) {
@@ -67,10 +65,10 @@
     }
 
     @Override
-    public void accept(ByteDataView data, String descriptors, DiagnosticsHandler handler) {
+    public void acceptClassFile(ClassFileConsumerData data) {
       try {
         Collection<Marker> markers =
-            ExtractMarker.extractMarkerFromClassProgramData(data.copyByteData());
+            ExtractMarker.extractMarkerFromClassProgramData(data.getByteDataCopy());
         assertEquals(1, markers.size());
         marker = markers.iterator().next();
       } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index da660e7..a1a6580 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -100,7 +100,8 @@
           DexVm.Version.V9_0_0,
           DexVm.Version.V10_0_0,
           DexVm.Version.V12_0_0,
-          DexVm.Version.V13_0_0);
+          DexVm.Version.V13_0_0,
+          DexVm.Version.V14_0_0);
 
   private static final String JUNIT_TEST_RUNNER = "org.junit.runner.JUnitCore";
   private static final String JUNIT_JAR = "third_party/junit/junit-4.13-beta-2.jar";
@@ -184,13 +185,15 @@
           .put(
               "098-ddmc",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V12_0_0, DexVm.Version.V13_0_0)))
+                  TestCondition.runtimes(
+                      DexVm.Version.V12_0_0, DexVm.Version.V13_0_0, DexVm.Version.V14_0_0)))
           // TODO(b/197079442): Triage - fails with "java.lang.NoSuchMethodException:
           //  org.apache.harmony.dalvik.ddmc.DdmVmInternal.enableRecentAllocations [boolean]"
           .put(
               "145-alloc-tracking-stress",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.Version.V12_0_0, DexVm.Version.V13_0_0)))
+                  TestCondition.runtimes(
+                      DexVm.Version.V12_0_0, DexVm.Version.V13_0_0, DexVm.Version.V14_0_0)))
           .build();
 
   // Tests that are flaky with the Art version we currently use.
@@ -496,6 +499,7 @@
   static {
     ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
     builder
+        .put(DexVm.Version.V14_0_0, ImmutableList.of("543-env-long-ref", "518-null-array-get"))
         .put(DexVm.Version.V13_0_0, ImmutableList.of("543-env-long-ref", "518-null-array-get"))
         .put(DexVm.Version.V12_0_0, ImmutableList.of("543-env-long-ref", "518-null-array-get"))
         .put(
@@ -832,7 +836,8 @@
                           DexVm.Version.V5_1_1,
                           DexVm.Version.V6_0_1,
                           DexVm.Version.V7_0_0,
-                          DexVm.Version.V13_0_0)),
+                          DexVm.Version.V13_0_0,
+                          DexVm.Version.V14_0_0)),
                   TestCondition.match(
                       TestCondition.compilers(
                           CompilerUnderTest.R8,
@@ -861,7 +866,8 @@
                       DexVm.Version.V9_0_0,
                       DexVm.Version.V10_0_0,
                       DexVm.Version.V12_0_0,
-                      DexVm.Version.V13_0_0)))
+                      DexVm.Version.V13_0_0,
+                      DexVm.Version.V14_0_0)))
           .put("454-get-vreg", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Fails: regs_jni.cc:42] Check failed: GetVReg(m, 0, kIntVReg, &value)
           // The R8/D8 code does not put values in the same registers as the tests expects.
@@ -878,7 +884,8 @@
                       DexVm.Version.V9_0_0,
                       DexVm.Version.V10_0_0,
                       DexVm.Version.V12_0_0,
-                      DexVm.Version.V13_0_0)))
+                      DexVm.Version.V13_0_0,
+                      DexVm.Version.V14_0_0)))
           .put("457-regs", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Class not found.
           .put("529-checker-unresolved", TestCondition.any())
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index a4cbbc2..a89ee46 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -132,29 +132,41 @@
   private static final Map<DexVm.Version, Map<String, String>> customProcessedOutputExpectation =
       ImmutableMap.of(
           Version.V4_4_4,
-              ImmutableMap.of(
-                  "bad-codegen", "java.lang.NullPointerException\n",
-                  "type-confusion-regression2", "java.lang.NullPointerException\n",
-                  "type-confusion-regression3", "java.lang.NullPointerException\n",
-                  "merge-blocks-regression", "java.lang.NullPointerException\n"),
+          ImmutableMap.of(
+              "bad-codegen", "java.lang.NullPointerException\n",
+              "type-confusion-regression2", "java.lang.NullPointerException\n",
+              "type-confusion-regression3", "java.lang.NullPointerException\n",
+              "merge-blocks-regression", "java.lang.NullPointerException\n"),
           Version.V4_0_4,
-              ImmutableMap.of(
-                  "bad-codegen", "java.lang.NullPointerException\n",
-                  "type-confusion-regression2", "java.lang.NullPointerException\n",
-                  "type-confusion-regression3", "java.lang.NullPointerException\n",
-                  "merge-blocks-regression", "java.lang.NullPointerException\n"),
+          ImmutableMap.of(
+              "bad-codegen", "java.lang.NullPointerException\n",
+              "type-confusion-regression2", "java.lang.NullPointerException\n",
+              "type-confusion-regression3", "java.lang.NullPointerException\n",
+              "merge-blocks-regression", "java.lang.NullPointerException\n"),
           Version.V13_0_0,
-              ImmutableMap.of(
-                  "bad-codegen",
-                      StringUtils.lines(
-                          "java.lang.NullPointerException: Attempt to read from field 'Test Test.a'"
-                              + " on a null object reference in method 'Test TestObject.a(Test,"
-                              + " Test, Test, Test, boolean)'"),
-                  "type-confusion-regression3",
-                      StringUtils.lines(
-                          "java.lang.NullPointerException: Attempt to read from field 'byte[]"
-                              + " Test.a' on a null object reference in method 'int"
-                              + " TestObject.a(Test, Test)'")));
+          ImmutableMap.of(
+              "bad-codegen",
+              StringUtils.lines(
+                  "java.lang.NullPointerException: Attempt to read from field 'Test Test.a'"
+                      + " on a null object reference in method 'Test TestObject.a(Test,"
+                      + " Test, Test, Test, boolean)'"),
+              "type-confusion-regression3",
+              StringUtils.lines(
+                  "java.lang.NullPointerException: Attempt to read from field 'byte[]"
+                      + " Test.a' on a null object reference in method 'int"
+                      + " TestObject.a(Test, Test)'")),
+          Version.V14_0_0,
+          ImmutableMap.of(
+              "bad-codegen",
+                  StringUtils.lines(
+                      "java.lang.NullPointerException: Attempt to read from field 'Test Test.a'"
+                          + " on a null object reference in method 'Test TestObject.a(Test,"
+                          + " Test, Test, Test, boolean)'"),
+              "type-confusion-regression3",
+                  StringUtils.lines(
+                      "java.lang.NullPointerException: Attempt to read from field 'byte[]"
+                          + " Test.a' on a null object reference in method 'int"
+                          + " TestObject.a(Test, Test)'")));
 
   // Tests where the input fails with a verification error on Dalvik instead of the
   // expected runtime exception.
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index e4cec10..a23c980 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -27,6 +27,7 @@
     ART_V10_0_0,
     ART_V12_0_0,
     ART_V13_0_0,
+    ART_V14_0_0,
     ART_DEFAULT,
     ART_MASTER,
     JAVA;
@@ -56,6 +57,8 @@
           return ART_V12_0_0;
         case V13_0_0:
           return ART_V13_0_0;
+        case V14_0_0:
+          return ART_V14_0_0;
         case DEFAULT:
           return ART_DEFAULT;
         case MASTER:
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index a05309c..c22f43e 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -105,6 +105,11 @@
     return false;
   }
 
+  public boolean runtimeWithClassValue() {
+    assert isCfRuntime() || isDexRuntime();
+    return isCfRuntime() || getDexRuntimeVersion().isNewerThanOrEqual(DexVm.Version.V14_0_0);
+  }
+
   // Convenience predicates.
   public boolean isDexRuntime() {
     return runtime.isDex();
@@ -127,7 +132,11 @@
   }
 
   public boolean isDexRuntimeVersion(DexVm.Version vm) {
-    return isDexRuntime() && vm == getDexRuntimeVersion();
+    return isDexRuntime() && getDexRuntimeVersion().isEqualTo(vm);
+  }
+
+  public boolean isDexRuntimeVersionNewerThanOrEqual(DexVm.Version vm) {
+    return isDexRuntime() && getDexRuntimeVersion().isNewerThanOrEqual(vm);
   }
 
   public boolean isNoneRuntime() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 459fbce..51ed733 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -92,13 +92,26 @@
 
   static final Path[] EMPTY_PATH = {};
 
+  public static boolean isNewGradleSetup() {
+    return "true".equals(System.getenv("USE_NEW_GRADLE_SETUP"));
+  }
+
+  public static String getProjectRoot() {
+    String property = System.getProperty("user.dir");
+    if (property.endsWith("d8_r8/test")) {
+      return "../../";
+    }
+    return "";
+  }
+
   public static final String SOURCE_DIR = "src/main/java/";
   public static final String RESOURCES_DIR = "src/main/resources/";
   public static final String BUILD_DIR = "build/";
+  public static final String TEST_MODULE_DIR = getProjectRoot() + "d8_r8/test_modules/";
   public static final String GENERATED_TEST_BUILD_DIR = BUILD_DIR + "generated/test/";
   public static final String LIBS_DIR = BUILD_DIR + "libs/";
-  public static final String THIRD_PARTY_DIR = "third_party/";
-  public static final String TOOLS_DIR = "tools/";
+  public static final String THIRD_PARTY_DIR = getProjectRoot() + "third_party/";
+  public static final String TOOLS_DIR = getProjectRoot() + "tools/";
   public static final String TESTS_DIR = "src/test/";
   public static final String TESTS_SOURCE_DIR = "src/test/java";
   public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
@@ -137,29 +150,33 @@
   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
 
   public static final String CORE_LAMBDA_STUBS =
-      "third_party/core-lambda-stubs/core-lambda-stubs.jar";
-  public static final String JSR223_RI_JAR = "third_party/jsr223-api-1.0/jsr223-api-1.0.jar";
+      THIRD_PARTY_DIR + "core-lambda-stubs/core-lambda-stubs.jar";
+  public static final String JSR223_RI_JAR = THIRD_PARTY_DIR + "jsr223-api-1.0/jsr223-api-1.0.jar";
   public static final String RHINO_ANDROID_JAR =
-      "third_party/rhino-android-1.1.1/rhino-android-1.1.1.jar";
-  public static final String RHINO_JAR = "third_party/rhino-1.7.10/rhino-1.7.10.jar";
+      THIRD_PARTY_DIR + "rhino-android-1.1.1/rhino-android-1.1.1.jar";
+  public static final String RHINO_JAR = THIRD_PARTY_DIR + "rhino-1.7.10/rhino-1.7.10.jar";
   public static final String K2JVMCompiler = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler";
-  private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
+  private static final String ANDROID_JAR_PATTERN =
+      THIRD_PARTY_DIR + "android_jar/lib-v%d/android.jar";
   private static final String ANDROID_API_VERSIONS_XML_PATTERN =
-      "third_party/android_jar/lib-v%d/api-versions.xml";
+      THIRD_PARTY_DIR + "android_jar/lib-v%d/api-versions.xml";
   private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
 
-  public static final String OPEN_JDK_DIR = "third_party/openjdk/";
+  public static final String OPEN_JDK_DIR = THIRD_PARTY_DIR + "openjdk/";
   public static final String JAVA_8_RUNTIME = OPEN_JDK_DIR + "openjdk-rt-1.8/rt.jar";
   public static final String JDK_11_TESTS_DIR = OPEN_JDK_DIR + "jdk-11-test/";
   public static final String JDK_11_TIME_TESTS_DIR = JDK_11_TESTS_DIR + "java/time/";
 
-  private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard";
-  private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
+  private static final String PROGUARD5_2_1 =
+      THIRD_PARTY_DIR + "proguard/proguard5.2.1/bin/proguard";
+  private static final String PROGUARD6_0_1 =
+      THIRD_PARTY_DIR + "proguard/proguard6.0.1/bin/proguard";
   private static final String PROGUARD = PROGUARD5_2_1;
   public static final Path JACOCO_ROOT = Paths.get("third_party", "jacoco", "0.8.6");
   public static final Path JACOCO_AGENT = JACOCO_ROOT.resolve(Paths.get("lib", "jacocoagent.jar"));
   public static final Path JACOCO_CLI = JACOCO_ROOT.resolve(Paths.get("lib", "jacococli.jar"));
-  public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS = "third_party/proguardsettings/";
+  public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS =
+      THIRD_PARTY_DIR + "proguardsettings/";
 
   public static final Path RETRACE_MAPS_DIR = Paths.get(THIRD_PARTY_DIR, "r8mappings");
 
@@ -256,6 +273,8 @@
     ART_12_0_0_HOST(Version.V12_0_0, Kind.HOST),
     ART_13_0_0_TARGET(Version.V13_0_0, Kind.TARGET),
     ART_13_0_0_HOST(Version.V13_0_0, Kind.HOST),
+    ART_14_0_0_TARGET(Version.V14_0_0, Kind.TARGET),
+    ART_14_0_0_HOST(Version.V14_0_0, Kind.HOST),
     ART_MASTER_TARGET(Version.MASTER, Kind.TARGET),
     ART_MASTER_HOST(Version.MASTER, Kind.HOST);
 
@@ -276,6 +295,7 @@
       V10_0_0("10.0.0"),
       V12_0_0("12.0.0"),
       V13_0_0("13.0.0"),
+      V14_0_0("14.0.0"),
       MASTER("master");
 
       /** This should generally be the latest DEX VM fully supported. */
@@ -338,7 +358,7 @@
       }
 
       public static Version last() {
-        return V13_0_0;
+        return V14_0_0;
       }
 
       public static Version master() {
@@ -609,12 +629,11 @@
     }
   }
 
-  private static final String TOOLS = "tools";
-
   private static final Map<DexVm, String> ART_DIRS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "art")
           .put(DexVm.ART_MASTER_HOST, "host/art-master")
+          .put(DexVm.ART_14_0_0_HOST, "host/art-14.0.0-dp1")
           .put(DexVm.ART_13_0_0_HOST, "host/art-13.0.0")
           .put(DexVm.ART_12_0_0_HOST, "host/art-12.0.0-beta4")
           .put(DexVm.ART_10_0_0_HOST, "art-10.0.0")
@@ -630,6 +649,7 @@
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "bin/art")
           .put(DexVm.ART_MASTER_HOST, "bin/art")
+          .put(DexVm.ART_14_0_0_HOST, "bin/art")
           .put(DexVm.ART_13_0_0_HOST, "bin/art")
           .put(DexVm.ART_12_0_0_HOST, "bin/art")
           .put(DexVm.ART_10_0_0_HOST, "bin/art")
@@ -645,6 +665,7 @@
   private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "bin/art")
+          .put(DexVm.ART_14_0_0_HOST, "bin/art")
           .put(DexVm.ART_13_0_0_HOST, "bin/art")
           .put(DexVm.ART_12_0_0_HOST, "bin/art")
           .put(DexVm.ART_10_0_0_HOST, "bin/art")
@@ -679,6 +700,7 @@
     ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS)
+        .put(DexVm.ART_14_0_0_HOST, NEWER_ART_BOOT_LIBS)
         .put(DexVm.ART_13_0_0_HOST, NEWER_ART_BOOT_LIBS)
         .put(DexVm.ART_12_0_0_HOST, NEWER_ART_BOOT_LIBS)
         .put(DexVm.ART_10_0_0_HOST, ART_BOOT_LIBS)
@@ -698,6 +720,7 @@
     ImmutableMap.Builder<DexVm, String> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, "angler")
+        .put(DexVm.ART_14_0_0_HOST, "redfin")
         .put(DexVm.ART_13_0_0_HOST, "redfin")
         .put(DexVm.ART_12_0_0_HOST, "redfin")
         .put(DexVm.ART_10_0_0_HOST, "coral")
@@ -715,7 +738,7 @@
 
   private static Path getDexVmPath(DexVm vm) {
     DexVm.Version version = vm.getVersion();
-    Path base = Paths.get(TOOLS, "linux");
+    Path base = Paths.get(TOOLS_DIR, "linux");
     switch (version) {
       case DEFAULT:
         return base.resolve("art");
@@ -731,6 +754,7 @@
       case V12_0_0:
         return base.resolve("host").resolve("art-12.0.0-beta4");
       case V13_0_0:
+      case V14_0_0:
       case MASTER:
         return base.resolve("host").resolve("art-" + version);
       default:
@@ -765,6 +789,7 @@
         return "arm64";
       case V12_0_0:
       case V13_0_0:
+      case V14_0_0:
       case MASTER:
         return "x86_64";
       default:
@@ -772,10 +797,6 @@
     }
   }
 
-  private static Path getProductBootImagePath(DexVm vm) {
-    return getProductPath(vm).resolve("system").resolve("framework").resolve("boot.art");
-  }
-
   public static byte[] getClassAsBytes(Class clazz) throws IOException {
     return Files.readAllBytes(getClassFileForTestClass(clazz));
   }
@@ -799,7 +820,7 @@
     }
     if (isLinux() || isMac()) {
       // The Linux version is used on Mac, where it is run in a Docker container.
-      return TOOLS + "/linux/" + dir;
+      return TOOLS_DIR + "/linux/" + dir;
     }
     fail("Unsupported platform, we currently only support mac and linux: " + getPlatform());
     return ""; //never here
@@ -840,7 +861,7 @@
   private static Path getDxExecutablePath() {
     String toolsDir = toolsDir();
     String executableName = toolsDir.equals("windows") ? "dx.bat" : "dx";
-    return Paths.get(TOOLS, toolsDir(), "dx", "bin", executableName);
+    return Paths.get(TOOLS_DIR, toolsDir(), "dx", "bin", executableName);
   }
 
   public static String getArtBinary(DexVm version) {
@@ -895,7 +916,7 @@
 
   private static Path getAndroidJarPath(AndroidApiLevel apiLevel) {
     if (apiLevel == AndroidApiLevel.MASTER) {
-      return Paths.get("third_party/android_jar/lib-master/android.jar");
+      return Paths.get(THIRD_PARTY_DIR + "android_jar/lib-master/android.jar");
     }
     String jar = String.format(
         ANDROID_JAR_PATTERN,
@@ -1058,6 +1079,8 @@
     switch (dexVm.version) {
       case MASTER:
         return AndroidApiLevel.MASTER;
+      case V14_0_0:
+        return AndroidApiLevel.U;
       case V13_0_0:
         return AndroidApiLevel.T;
       case V12_0_0:
@@ -1135,7 +1158,11 @@
   }
 
   public static Path getClassPathForTests() {
-    return Paths.get(BUILD_DIR, "classes", "java", "test");
+    if (isNewGradleSetup()) {
+      return Paths.get(TEST_MODULE_DIR, "tests_java_8", "build", "classes", "java", "main");
+    } else {
+      return Paths.get(BUILD_DIR, "classes", "java", "test");
+    }
   }
 
   private static List<String> getNamePartsForTestPackage(Package pkg) {
@@ -1201,7 +1228,7 @@
   public static Collection<Path> getClassFilesForInnerClasses(Collection<Class<?>> classes)
       throws IOException {
     Set<Path> paths = new HashSet<>();
-    for (Class clazz : classes) {
+    for (Class<?> clazz : classes) {
       Path path = ToolHelper.getClassFileForTestClass(clazz);
       String prefix = path.toString().replace(CLASS_EXTENSION, "$");
       paths.addAll(
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
index cf867dd..df687ed 100644
--- a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -68,7 +68,7 @@
       Paths.get(ToolHelper.SOURCE_DIR)
           .resolve(PACKAGE_NAME.replace('.', '/'))
           .resolve(CLASS_NAME + ".java");
-  private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.T;
+  private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.U;
 
   @Parameter public TestParameters parameters;
 
@@ -79,7 +79,7 @@
 
   @Test
   public void testLibCoreNeedsUpgrading() {
-    assertEquals(GENERATED_FOR_API_LEVEL, AndroidApiLevel.LATEST);
+    assertEquals(GENERATED_FOR_API_LEVEL, AndroidApiLevel.API_DATABASE_LEVEL);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
index 091cff8..f247e38 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
@@ -68,7 +68,10 @@
         .compile()
         .inspect(this::computeValuePresent)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+        .applyIf(
+            parameters.runtimeWithClassValue(),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
   }
 
   @Test
@@ -83,7 +86,7 @@
         .inspect(this::computeValueAbsent)
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
-            parameters.isCfRuntime(),
+            parameters.runtimeWithClassValue(),
             r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
             r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
   }
@@ -132,7 +135,7 @@
         .inspect(this::computeValueAbsent)
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
-            parameters.isCfRuntime(),
+            parameters.runtimeWithClassValue(),
             r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
             r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
   }
@@ -177,7 +180,7 @@
           .inspect(this::computeValuePresent)
           .run(parameters.getRuntime(), TestClass.class)
           .applyIf(
-              parameters.isCfRuntime(),
+              parameters.runtimeWithClassValue(),
               r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
               r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
     }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
index 2673e46..13a5073 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.cf.varhandle;
 
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.examples.jdk9.VarHandle;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
@@ -73,6 +74,9 @@
   @Override
   protected String getExpectedOutputForArtImplementation() {
     assert parameters.isDexRuntime();
-    return StringUtils.lines(EXPECTED_OUTPUT.trim(), "Art implementation", "Art implementation");
+    return parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V14_0_0)
+        ? StringUtils.lines(
+            EXPECTED_OUTPUT.trim(), "Reference implementation", "Reference implementation")
+        : StringUtils.lines(EXPECTED_OUTPUT.trim(), "Art implementation", "Art implementation");
   }
 }
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 fd3e16d..f4ef14b 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
 import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest;
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
+import com.android.tools.r8.compilerapi.dexconsumers.PerClassSyntheticContextsTest;
 import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest;
 import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
 import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
@@ -58,7 +59,8 @@
           ArtProfilesForRewritingApiTest.ApiTest.class,
           StartupProfileApiTest.ApiTest.class,
           ClassConflictResolverTest.ApiTest.class,
-          ProguardKeepRuleDiagnosticsApiTest.ApiTest.class);
+          ProguardKeepRuleDiagnosticsApiTest.ApiTest.class,
+          PerClassSyntheticContextsTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/dexconsumers/PerClassSyntheticContextsTest.java b/src/test/java/com/android/tools/r8/compilerapi/dexconsumers/PerClassSyntheticContextsTest.java
new file mode 100644
index 0000000..69f7b9b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/dexconsumers/PerClassSyntheticContextsTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.dexconsumers;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumerData;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumerData;
+import com.android.tools.r8.DiagnosticsHandler;
+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.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class PerClassSyntheticContextsTest extends CompilerApiTestRunner {
+
+  public PerClassSyntheticContextsTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void test() throws Exception {
+    // First compile to CF such that we have an input class that has a synthetic context.
+    ClassReference backport = SyntheticItemsTestUtils.syntheticBackportClass(UsesBackport.class, 0);
+    Map<String, byte[]> outputs = new HashMap<>();
+    testForD8(Backend.CF)
+        .addProgramClasses(UsesBackport.class)
+        .setIntermediate(true)
+        .setMinApi(1)
+        .setProgramConsumer(
+            new ClassFileConsumer() {
+
+              @Override
+              public void acceptClassFile(ClassFileConsumerData data) {
+                outputs.put(data.getClassDescriptor(), data.getByteDataCopy());
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {}
+            })
+        .compile()
+        .writeToZip();
+    // Run using the API test to obtain the backport context.
+    new ApiTest(ApiTest.PARAMETERS)
+        .run(
+            outputs.get(backport.getDescriptor()),
+            context -> assertEquals(descriptor(UsesBackport.class), context));
+  }
+
+  public static class UsesBackport {
+    public static void foo() {
+      Boolean.compare(true, false);
+    }
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void run(byte[] input, Consumer<String> syntheticContext) throws Exception {
+      D8.run(
+          D8Command.builder()
+              .addClassProgramData(input, Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setMinApiLevel(1)
+              .setProgramConsumer(
+                  new DexFilePerClassFileConsumer() {
+                    @Override
+                    public void acceptDexFile(DexFilePerClassFileConsumerData data) {
+                      syntheticContext.accept(data.getSynthesizingContextForPrimaryClass());
+                    }
+
+                    @Override
+                    public void finished(DiagnosticsHandler handler) {
+                      // nothing to finish up.
+                    }
+                  })
+              .build());
+    }
+
+    @Test
+    public void test() throws Exception {
+      byte[] input = getBytesForClass(getMockClass());
+      run(
+          input,
+          context -> {
+            if (context != null) {
+              throw new RuntimeException("unexpected");
+            }
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/d8/DexVersionTests.java b/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
index f413f98..7c82964 100644
--- a/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
+++ b/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
@@ -5,18 +5,17 @@
 
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ByteDataView;
 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.DexIndexedConsumerData;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Set;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -77,8 +76,7 @@
     boolean hasOutput = false;
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
       hasOutput = true;
     }
 
diff --git a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
index 51e8ab2..7a73a31 100644
--- a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
@@ -47,9 +47,12 @@
 
   @Test
   public void testDefaultMethod() throws Throwable {
-    // TODO(b/244683447): This test fails on ART 13 when checking current method in doSomething.
+    // TODO(b/244683447): This test fails on Art 13 and Art 14 when checking current method in
+    //  doSomething.
     assumeTrue(
-        parameters.isCfRuntime() || !parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0));
+        parameters.isCfRuntime()
+            || !(parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0)
+                || parameters.getDexRuntimeVersion().isEqualTo(Version.V14_0_0)));
     testForRuntime(parameters)
         .addProgramFiles(JAR)
         .run(parameters.getRuntime(), debuggeeClass)
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
index 901e412..2205c9c 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -259,7 +259,9 @@
   public void testNestedInlining() throws Throwable {
     assumeTrue(
         "b/244704042: Incorrect step-into StringBuilder.",
-        parameters.isCfRuntime() || !parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0));
+        parameters.isCfRuntime()
+            || !(parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0)
+                || parameters.getDexRuntimeVersion().isEqualTo(Version.V14_0_0)));
     // Count the number of lines in the source file. This is needed to check that inlined code
     // refers to non-existing line numbers.
     Path sourceFilePath =
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
index efcc639..2bc0090 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileDeprecatedAttribute.java
@@ -6,11 +6,10 @@
 
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileConsumer.ForwardingConsumer;
+import com.android.tools.r8.ClassFileConsumerData;
 import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -91,9 +90,8 @@
             .setProgramConsumer(
                 new ClassFileConsumer.ForwardingConsumer(null) {
                   @Override
-                  public void accept(
-                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-                    checkDeprecatedAttributes(data.getBuffer());
+                  public void acceptClassFile(ClassFileConsumerData data) {
+                    checkDeprecatedAttributes(data.getByteDataView().getBuffer());
                   }
                 })
             .compile();
@@ -128,8 +126,8 @@
       builder.setProgramConsumer(
           new ForwardingConsumer(null) {
             @Override
-            public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-              checkDeprecatedAttributes(data.getBuffer());
+            public void acceptClassFile(ClassFileConsumerData data) {
+              checkDeprecatedAttributes(data.getByteDataView().getBuffer());
             }
           });
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index ed2dd4e..9be08a2 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -10,11 +10,11 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumerData;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DexFilePerClassFileConsumerData;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -247,21 +247,16 @@
             firstRoundOutput.isCf()
                 ? new ClassFileConsumer.ForwardingConsumer(null) {
                   @Override
-                  public void accept(
-                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-                    byte[] bytes = data.copyByteData();
+                  public void acceptClassFile(ClassFileConsumerData data) {
+                    byte[] bytes = data.getByteDataCopy();
                     assert bytes != null;
                     outputsRoundOne.add(bytes);
                   }
                 }
                 : new DexFilePerClassFileConsumer.ForwardingConsumer(null) {
                   @Override
-                  public void accept(
-                      String primaryClassDescriptor,
-                      ByteDataView data,
-                      Set<String> descriptors,
-                      DiagnosticsHandler handler) {
-                    byte[] bytes = data.copyByteData();
+                  public void acceptDexFile(DexFilePerClassFileConsumerData data) {
+                    byte[] bytes = data.getByteDataCopy();
                     assert bytes != null;
                     outputsRoundOne.add(bytes);
                   }
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..4a55fc2 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
@@ -9,8 +9,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumerData;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.GenerateMainDexListRunResult;
 import com.android.tools.r8.OutputMode;
@@ -361,13 +361,12 @@
     Set<String> mainDexDescriptors;
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
-      if (fileIndex == 0) {
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
+      if (data.getFileIndex() == 0) {
         assertNull(mainDexBytes);
         assertNull(mainDexDescriptors);
-        mainDexBytes = data.copyByteData();
-        mainDexDescriptors = descriptors;
+        mainDexBytes = data.getByteDataCopy();
+        mainDexDescriptors = data.getClassDescriptors();
       }
     }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava17Test.java b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava17Test.java
index fde89f0..8f9332d 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava17Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava17Test.java
@@ -34,7 +34,6 @@
     super(parameters, Math.class, TEST_JAR, "backport.MathBackportJava17Main");
 
     // Math.absExact.
-    // The exact number needs to be updated once we test Android U.
-    registerTarget(AndroidApiLevel.U, 27);
+    registerTarget(AndroidApiLevel.U, 8);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava17Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava17Test.java
index d54a044..4a5345fe 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava17Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava17Test.java
@@ -35,7 +35,6 @@
   public ObjectsBackportJava17Test(TestParameters parameters) {
     super(parameters, Objects.class, TEST_JAR, TEST_CLASS);
     // Objects.checkFromIndexSize, Objects.checkFromToIndex, Objects.checkIndex.
-    // The exact number needs to be updated once we test Android U.
-    registerTarget(AndroidApiLevel.U, 28);
+    registerTarget(AndroidApiLevel.U, 21);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java
index 073c179..5e33276 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/StreamBackportJava9Test.java
@@ -34,12 +34,15 @@
 
   public StreamBackportJava9Test(TestParameters parameters) {
     super(parameters, Stream.class, TEST_JAR, "backport.StreamBackportJava9Main");
-    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
-    // an actual API level, migrate these tests to StreamBackportTest.
+    // Note: The methods in this test exist from Android U. However, they are only available from
+    // Java 9. When tests build with Java 9 migrate to StreamBackportTest and add insert
+    // StreamBackportJava9Main as an inner class here.
 
     // Available since N as part of library desugaring.
     ignoreInvokes("of");
     ignoreInvokes("empty");
     ignoreInvokes("count");
+
+    registerTarget(AndroidApiLevel.U, 2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava17Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava17Test.java
index 3089d40..c93b1ee 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava17Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava17Test.java
@@ -33,7 +33,6 @@
 
   public StrictMathBackportJava17Test(TestParameters parameters) {
     super(parameters, StrictMath.class, TEST_JAR, "backport.StrictMathBackportJava17Main");
-    // The exact number needs to be updated once we test Android U.
-    registerTarget(AndroidApiLevel.U, 75);
+    registerTarget(AndroidApiLevel.U, 30);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java
index 16021f7..089553b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DateTimeFormatterTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.StringUtils;
@@ -29,6 +30,11 @@
       StringUtils.lines("2/3/01 4:05 AM - Feb 3, 1 4:05 AM");
   private static final String expectedOutput =
       StringUtils.lines("2/3/01, 4:05 AM - Feb 3, 1, 4:05 AM");
+  // From ICU 72, see https://android-review.git.corp.google.com/c/platform/libcore/+/2292140
+  private static final String expectedOutputDesugaredLibNNBSP =
+      StringUtils.lines("2/3/01 4:05\u202FAM - Feb 3, 1 4:05\u202FAM");
+  private static final String expectedOutputNNBSP =
+      StringUtils.lines("2/3/01, 4:05\u202FAM - Feb 3, 1, 4:05\u202FAM");
 
   private final TestParameters parameters;
   private final CompilationSpecification compilationSpecification;
@@ -61,9 +67,15 @@
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccess();
     if (libraryDesugaringSpecification.hasTimeDesugaring(parameters)) {
-      run.assertSuccessWithOutput(expectedOutputDesugaredLib);
+      run.assertSuccessWithOutput(
+          parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V14_0_0)
+              ? expectedOutputDesugaredLibNNBSP
+              : expectedOutputDesugaredLib);
     } else {
-      run.assertSuccessWithOutput(expectedOutput);
+      run.assertSuccessWithOutput(
+          parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V14_0_0)
+              ? expectedOutputNNBSP
+              : expectedOutput);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
index 3fd5104..2f65784 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmptyDesugaredLibrary.java
@@ -9,9 +9,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DexIndexedConsumerData;
 import com.android.tools.r8.L8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
@@ -22,7 +21,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
-import java.util.Set;
 import java.util.zip.ZipFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -67,8 +65,7 @@
     }
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
       count++;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
index 8b67b20..a2c95c1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
@@ -169,7 +169,6 @@
         "test.java.time.TestOffsetTime",
         "test.java.time.TestClock_Offset",
         "test.java.time.TestPeriod",
-        "test.java.time.TestClock_System",
         "test.java.time.TestOffsetDateTime_instants",
         "test.java.time.temporal.TestDateTimeBuilderCombinations",
         "test.java.time.temporal.TestJulianFields",
@@ -180,6 +179,12 @@
       new String[] {"tck.java.time.TestIsoChronology"};
   static final String[] RAW_TEMPORAL_SUCCESSES_UP_TO_11 =
       new String[] {"test.java.time.temporal.TestIsoWeekFields"};
+  static final String[] RAW_TEMPORAL_SUCCESSES_UP_TO_14 =
+      new String[] {
+        // Reflective lookup Class.forName("java.time.Clock$SystemClock").getDeclaredField("offset")
+        // fails.
+        "test.java.time.TestClock_System"
+      };
   static final String[] FORMAT_CHRONO_SUCCESSES =
       new String[] {
         "test.java.time.format.TestFractionPrinterParser",
@@ -230,6 +235,10 @@
       // In 12 some ISO is supported that other versions do not support.
       Collections.addAll(allTests, RAW_TEMPORAL_SUCCESSES_UP_TO_11);
     }
+    if (parameters.getDexRuntimeVersion().isOlderThan(Version.V14_0_0)) {
+      // In 14 some reflection used in test fails.
+      Collections.addAll(allTests, RAW_TEMPORAL_SUCCESSES_UP_TO_14);
+    }
     // The bridge is always present with JDK11 due to partial desugaring between 26 and 33.
     // On JDK8 the bridge is absent in between 26 and 33.
     if (libraryDesugaringSpecification != JDK8
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
index d80d81e..541bfea 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
@@ -4,9 +4,11 @@
 package com.android.tools.r8.desugar.graph;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.origin.GlobalSyntheticOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
@@ -96,6 +98,12 @@
 
   @Override
   public synchronized void accept(Origin dependent, Origin dependency) {
+    // D8/R8 should not report edges synthetic origin.
+    assertNotEquals(dependent, GlobalSyntheticOrigin.instance());
+    assertNotEquals(dependency, GlobalSyntheticOrigin.instance());
+    // D8/R8 may report edges to unknown origin, but that is typically *not* what should be done.
+    assertNotEquals(dependency, Origin.unknown());
+    assertNotEquals(dependent, Origin.unknown());
     assertFalse(finished);
     dependents.computeIfAbsent(dependency, s -> new HashSet<>()).add(dependent);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
index 08e9523..bb58476 100644
--- a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
@@ -144,7 +144,7 @@
         .addProgramFiles(Jdk8272564.jar())
         .run(parameters.getRuntime(), Jdk8272564.Main.typeName())
         .applyIf(
-            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.P),
+            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.S),
             b -> b.inspect(this::assertJdk8272564NotFixedCode),
             b -> b.inspect(this::assertJdk8272564FixedCode))
         .assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeInterfaceTest.java
index 3b6ac61..9c36e69 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeInterfaceTest.java
@@ -77,7 +77,7 @@
   public void testD8() throws Exception {
     parameters.assumeDexRuntime();
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForD8()
         .addProgramClassFileData(
             dumpHost(),
@@ -111,8 +111,8 @@
   @Test
   public void testD8WithClasspathAndMerge() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     Path host =
         testForD8()
@@ -199,8 +199,8 @@
   @Test
   public void testD8WithoutMembersOnClasspath() {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     assertThrows(
         CompilationFailedException.class,
@@ -220,8 +220,8 @@
   @Test
   public void testD8WithoutHostOnClasspath() {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     assertThrows(
         CompilationFailedException.class,
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeSuperTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeSuperTest.java
index b629ed8..36290f2 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeSuperTest.java
@@ -69,7 +69,7 @@
   public void testD8() throws Exception {
     parameters.assumeDexRuntime();
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForD8()
         .addProgramClassFileData(dumpHost(), dumpMember(), dumpSubMember())
         .setMinApi(parameters)
@@ -101,8 +101,8 @@
   @Test
   public void testD8WithClasspathAndMerge() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     Path host =
         testForD8()
@@ -188,8 +188,8 @@
   @Test
   public void testD8WithoutMembersOnClasspath() {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     assertThrows(
         CompilationFailedException.class,
@@ -209,8 +209,8 @@
   @Test
   public void testD8WithoutHostOnClasspath() {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     assertThrows(
         CompilationFailedException.class,
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeVirtualTest.java
index 6e516a4..9e2d0c0 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexRewriteInvokeVirtualTest.java
@@ -69,7 +69,7 @@
   public void testD8() throws Exception {
     parameters.assumeDexRuntime();
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForD8()
         .addProgramClassFileData(dumpHost(), dumpMember1(), dumpMember2())
         .setMinApi(parameters)
@@ -101,8 +101,8 @@
   @Test
   public void testD8WithClasspathAndMerge() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     Path host =
         testForD8()
@@ -188,8 +188,8 @@
   @Test
   public void testD8WithoutMembersOnClasspath() {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     assertThrows(
         CompilationFailedException.class,
@@ -209,8 +209,8 @@
   @Test
   public void testD8WithoutHostOnClasspath() {
     assumeTrue(parameters.isDexRuntime());
-    // TODO(sgjesse): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
 
     assertThrows(
         CompilationFailedException.class,
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
index 5bd24bf..4a1e82c 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
@@ -81,7 +81,7 @@
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForR8(parameters.getBackend())
         .addProgramClassFileData(
             dumpHost(ACC_PRIVATE), dumpMember1(ACC_PRIVATE), dumpMember2(ACC_PRIVATE))
@@ -99,7 +99,7 @@
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForR8(parameters.getBackend())
         .addProgramClassFileData(
             dumpHost(ACC_PRIVATE), dumpMember1(ACC_PRIVATE), dumpMember2(ACC_PRIVATE))
@@ -121,7 +121,7 @@
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForR8(parameters.getBackend())
         .addProgramClassFileData(
             dumpHost(ACC_PUBLIC), dumpMember1(ACC_PUBLIC), dumpMember2(ACC_PUBLIC))
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
index 8d924dc..eca6260 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
@@ -80,7 +80,7 @@
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForR8(parameters.getBackend())
         .addProgramClassFileData(
             dumpHost(ACC_PRIVATE), dumpMember1(ACC_PRIVATE), dumpMember2(ACC_PRIVATE))
@@ -98,7 +98,7 @@
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForR8(parameters.getBackend())
         .addProgramClassFileData(
             dumpHost(ACC_PRIVATE), dumpMember1(ACC_PRIVATE), dumpMember2(ACC_PRIVATE))
@@ -120,7 +120,7 @@
     parameters.assumeR8TestParameters();
     assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
     // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
-    assertFalse(parameters.getApiLevel().getLevel() > 33);
+    assertFalse(parameters.getApiLevel().getLevel() > 34);
     testForR8(parameters.getBackend())
         .addProgramClassFileData(
             dumpHost(ACC_PUBLIC), dumpMember1(ACC_PUBLIC), dumpMember2(ACC_PUBLIC))
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
index 1c740a4..74ea12f 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member1;
 import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member2;
 import com.android.tools.r8.transformers.ClassFileTransformer;
@@ -62,13 +63,18 @@
   private void checkResult(TestRunResult<?> result) {
     if (isRuntimeWithNestSupport(parameters.getRuntime())) {
       result.assertSuccessWithOutput(EXPECTED_OUTPUT);
+    } else if (parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V14_0_0)) {
+      // TODO(b/247047415): Partial DEX support in Android U DP1 (reflective APIs).
+      result.assertSuccessWithOutput(R8_EXPECTED_OUTPUT);
     } else {
       result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
     }
   }
 
   private void checkResultR8(TestRunResult<?> result) {
-    if (isRuntimeWithNestSupport(parameters.getRuntime())) {
+    // TODO(b/247047415): Partial DEX support in Android U DP1 (reflective APIs).
+    if (parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V14_0_0)
+        || isRuntimeWithNestSupport(parameters.getRuntime())) {
       result.assertSuccessWithOutput(R8_EXPECTED_OUTPUT);
     } else {
       result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
new file mode 100644
index 0000000..2136f70
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInterfaceTest.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.records;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.desugar.graph.DesugarGraphTestConsumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RecordInterfaceTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordInterface";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT = StringUtils.lines("Human");
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private boolean isCfRuntimeWithNativeRecordSupport() {
+    return parameters.isCfRuntime()
+        && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK14)
+        && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(isCfRuntimeWithNativeRecordSupport());
+    testForJvm(parameters)
+        .addProgramClassFileData(PROGRAM_DATA)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(PROGRAM_DATA)
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8Intermediate() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+    GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+    Path path = compileIntermediate(globals);
+    testForD8()
+        .addProgramFiles(path)
+        .apply(
+            b ->
+                b.getBuilder()
+                    .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
+        .apply(b -> b.getBuilder().setDesugarGraphConsumer(consumer))
+        .setMinApi(parameters)
+        .setIncludeClassesChecksum(true)
+        .compile()
+        .assertNoMessages()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+    assertNoEdgeToRecord(consumer);
+  }
+
+  @Test
+  public void testD8IntermediateNoDesugaringInStep2() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+    GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+    Path path = compileIntermediate(globals);
+    testForD8()
+        .addProgramFiles(path)
+        .apply(
+            b ->
+                b.getBuilder()
+                    .addGlobalSyntheticsResourceProviders(globals.getIndexedModeProvider()))
+        .apply(b -> b.getBuilder().setDesugarGraphConsumer(consumer))
+        .setMinApi(parameters)
+        .setIncludeClassesChecksum(true)
+        // In Android Studio they disable desugaring at this point to improve build speed.
+        .disableDesugaring()
+        .compile()
+        .assertNoMessages()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+    assertNoEdgeToRecord(consumer);
+  }
+
+  private Path compileIntermediate(GlobalSyntheticsConsumer globalSyntheticsConsumer)
+      throws Exception {
+    Origin fake = new PathOrigin(Paths.get("origin"));
+    DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+    Path intermediate =
+        testForD8(Backend.DEX)
+            .apply(
+                b -> {
+                  // We avoid unknown origin here since they are not allowed when using a Graph
+                  // consumer.
+                  for (byte[] programDatum : PROGRAM_DATA) {
+                    b.getBuilder().addClassProgramData(programDatum, fake);
+                  }
+                })
+            .setMinApi(parameters)
+            .setIntermediate(true)
+            .setIncludeClassesChecksum(true)
+            .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globalSyntheticsConsumer))
+            .apply(b -> b.getBuilder().setDesugarGraphConsumer(consumer))
+            .compile()
+            .assertNoMessages()
+            .writeToZip();
+    assertNoEdgeToRecord(consumer);
+    return intermediate;
+  }
+
+  private void assertNoEdgeToRecord(DesugarGraphTestConsumer consumer) {
+    assertEquals(0, consumer.totalEdgeCount());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters)
+            .addKeepMainRule(MAIN_TYPE);
+    if (parameters.isCfRuntime()) {
+      builder
+          .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+          .compile()
+          .inspect(RecordTestUtils::assertRecordsAreRecords)
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      return;
+    }
+    builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
index 32cee2a..cc01489 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -72,8 +73,11 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        // No Art versions have support for sealed classes yet.
-        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+        .applyIf(
+            // TODO(b/270941147): Partial DEX support in Android U DP1 (reflective APIs).
+            parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V14_0_0),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
   }
 
   public Collection<byte[]> getTransformedClasses() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 1d8821e..3988efd 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -3,10 +3,8 @@
 import static junit.framework.TestCase.fail;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
@@ -92,17 +90,7 @@
 
     builder
         .addProgramResourceProvider(ArchiveResourceProvider.fromArchive(featureJar, true))
-        .setProgramConsumer(
-            new ArchiveConsumer(outputPath, true) {
-              @Override
-              public void accept(
-                  int fileIndex,
-                  ByteDataView data,
-                  Set<String> descriptors,
-                  DiagnosticsHandler handler) {
-                super.accept(fileIndex, data, descriptors, handler);
-              }
-            });
+        .setProgramConsumer(new ArchiveConsumer(outputPath, true));
   }
 
   public static FeatureSplit splitWithNonJavaFile(
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
index 475a8e7..c66a13d 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
@@ -44,7 +44,7 @@
         .applyIf(
             parameters.isCfRuntime(),
             r -> r.assertFailureWithErrorThatThrows(VerifyError.class),
-            !(parameters.isDexRuntimeVersion(Version.V13_0_0)
+            !(parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V13_0_0)
                 && parameters.canUseDefaultAndStaticInterfaceMethods()),
             r -> r.assertSuccessWithOutputLines("Hello World!"),
             r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
@@ -67,7 +67,7 @@
             parameters.isDexRuntime() && !parameters.canUseDefaultAndStaticInterfaceMethods(),
             r -> r.assertSuccessWithOutputLines("Hello World!"),
             parameters.isDexRuntime()
-                && !parameters.isDexRuntimeVersion(Version.V13_0_0)
+                && !parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V13_0_0)
                 && parameters.canUseDefaultAndStaticInterfaceMethods(),
             r -> r.assertFailureWithErrorThatThrows(NullPointerException.class),
             r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
index 63c62a5..8f1c4af 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import org.junit.Assume;
 import org.junit.Test;
 
@@ -51,10 +52,12 @@
         .get(ToolHelper.EXAMPLES_BUILD_DIR, folder, ToolHelper.DEFAULT_DEX_FILENAME).toString();
     Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
     app.writeToZipForTesting(generatedDexFile, OutputMode.DexIndexed);
-    String artOutput = ToolHelper
-        .checkArtOutputIdentical(originalDexFile, generatedDexFile.toString(), mainClass,
+    String artOutput =
+        ToolHelper.runArtNoVerificationErrors(
+            Collections.singletonList(generatedDexFile.toString()),
+            mainClass,
+            null,
             ToolHelper.getDexVm());
-
     // Compare with Java.
     ToolHelper.ProcessResult javaResult = ToolHelper.runJava(jarFile, mainClass);
     if (javaResult.exitCode != 0) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
index 6765723..a53ca72 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
@@ -94,7 +94,7 @@
   private List<String> getExpectedOutputLines() {
     if (parameters.isDexRuntime()) {
       if (parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)
-          || parameters.getDexRuntimeVersion().isEqualTo(Version.V13_0_0)) {
+          || parameters.isDexRuntimeVersionNewerThanOrEqual(Version.V13_0_0)) {
         return ImmutableList.of("true");
       }
     }
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index 61c7a61..15fccd2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -99,6 +99,7 @@
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticMessages()
         .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
+        .allowUnusedDontWarnJavaLangClassValue()
         .applyIf(
             parameters.isCfRuntime() && kotlinParameters.isNewerThanOrEqualTo(KOTLINC_1_8_0),
             TestShrinkerBuilder::addDontWarnJavaLangInvokeLambdaMetadataFactory)
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
index 9a4c93a..32568f9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
@@ -108,7 +108,8 @@
         .addKeepEnumsRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticMessages()
-        .allowUnusedDontWarnKotlinReflectJvmInternal();
+        .allowUnusedDontWarnKotlinReflectJvmInternal()
+        .allowUnusedDontWarnJavaLangClassValue();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 1abcddd..0bf9bc8 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -45,22 +45,11 @@
   private static final Path JAR_LIBRARY =
       Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "memberrebindinglib.jar");
 
-  private enum Frontend {
-    DEX, JAR;
-
-    @Override
-    public String toString() {
-      return this == DEX ? ".dex" : ".jar";
-    }
-  }
-
   private final String name;
-  private final Frontend kind;
+
   private final Backend backend;
-  private final Path originalDex;
   private final Path programFile;
   private final Consumer<CodeInspector> inspection;
-  private final Consumer<CodeInspector> originalInspection;
   private final int minApiLevel;
 
   @Rule
@@ -68,16 +57,9 @@
 
   public MemberRebindingTest(TestConfiguration configuration) {
     this.name = configuration.name;
-    this.kind = configuration.kind;
     this.backend = configuration.backend;
-    originalDex = configuration.getDexPath();
-    if (kind == Frontend.DEX) {
-      this.programFile = originalDex;
-    } else {
-      this.programFile = configuration.getJarPath();
-    }
+    this.programFile = configuration.getJarPath();
     this.inspection = configuration.processedInspection;
-    this.originalInspection = configuration.originalInspection;
     this.minApiLevel = configuration.getMinApiLevel();
   }
 
@@ -114,36 +96,6 @@
     return !invoke.holder().is("java.io.PrintStream");
   }
 
-  private static void inspectOriginalMain(CodeInspector inspector) {
-    MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
-        .method(CodeInspector.MAIN);
-    Iterator<InvokeInstructionSubject> iterator =
-        main.iterateInstructions(MemberRebindingTest::coolInvokes);
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
-    assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
-    assertTrue(iterator.next().holder().is("java.lang.System"));
-    assertTrue(iterator.next().holder().is("memberrebindinglib.AnIndependentInterface"));
-    assertTrue(iterator.next().holder().is("java.lang.System"));
-    assertTrue(
-        iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
-    assertTrue(
-        iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
-    assertFalse(iterator.hasNext());
-  }
-
   private static void inspectMain(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
         .method(CodeInspector.MAIN);
@@ -178,22 +130,6 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspectOriginalMain2(CodeInspector inspector) {
-    MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
-        .method(CodeInspector.MAIN);
-    Iterator<FieldAccessInstructionSubject> iterator =
-        main.iterateInstructions(InstructionSubject::isFieldAccess);
-    // Run through instance put, static put, instance get and instance get.
-    for (int i = 0; i < 4; i++) {
-      assertTrue(iterator.next().holder().is("memberrebinding2.ClassAtBottomOfChain"));
-      assertTrue(iterator.next().holder().is("memberrebinding2.ClassAtBottomOfChain"));
-      assertTrue(iterator.next().holder().is("memberrebinding2.ClassAtBottomOfChain"));
-      assertTrue(iterator.next().holder().is("memberrebinding2.subpackage.PublicClass"));
-    }
-    assertTrue(iterator.next().holder().is("java.lang.System"));
-    assertFalse(iterator.hasNext());
-  }
-
   private static void inspectMain2(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
         .method(CodeInspector.MAIN);
@@ -213,15 +149,6 @@
   public static MethodSignature TEST =
       new MethodSignature("test", "void", new String[]{});
 
-  private static void inspectOriginal3(CodeInspector inspector) {
-    MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
-    Iterator<InvokeInstructionSubject> iterator =
-        main.iterateInstructions(InstructionSubject::isInvoke);
-    assertTrue(iterator.next().holder().is("memberrebinding3.ClassAtBottomOfChain"));
-    assertTrue(iterator.next().holder().is("memberrebinding3.ClassAtBottomOfChain"));
-    assertTrue(iterator.next().holder().is("memberrebinding3.ClassAtBottomOfChain"));
-    assertFalse(iterator.hasNext());
-  }
 
   private static void inspect3(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
@@ -233,15 +160,6 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspectOriginal4(CodeInspector inspector) {
-    MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
-    Iterator<InvokeInstructionSubject> iterator =
-        main.iterateInstructions(InstructionSubject::isInvoke);
-    assertTrue(iterator.next().holder().is("memberrebinding4.Memberrebinding$Inner"));
-    assertTrue(iterator.next().holder().is("memberrebinding4.subpackage.PublicInterface"));
-    assertFalse(iterator.hasNext());
-  }
-
   private static void inspect4(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
     Iterator<InvokeInstructionSubject> iterator =
@@ -259,24 +177,18 @@
     }
 
     final String name;
-    final Frontend kind;
     final Backend backend;
     final AndroidVersion version;
-    final Consumer<CodeInspector> originalInspection;
     final Consumer<CodeInspector> processedInspection;
 
     private TestConfiguration(
         String name,
-        Frontend kind,
         Backend backend,
         AndroidVersion version,
-        Consumer<CodeInspector> originalInspection,
         Consumer<CodeInspector> processedInspection) {
       this.name = name;
-      this.kind = kind;
       this.backend = backend;
       this.version = version;
-      this.originalInspection = originalInspection;
       this.processedInspection = processedInspection;
     }
 
@@ -285,16 +197,8 @@
         String name,
         Backend backend,
         AndroidVersion version,
-        Consumer<CodeInspector> originalInspection,
         Consumer<CodeInspector> processedInspection) {
-      if (version == AndroidVersion.PRE_N && backend == Backend.DEX) {
-        builder.add(
-            new TestConfiguration(
-                name, Frontend.DEX, backend, version, originalInspection, processedInspection));
-      }
-      builder.add(
-          new TestConfiguration(
-              name, Frontend.JAR, backend, version, originalInspection, processedInspection));
+      builder.add(new TestConfiguration(name, backend, version, processedInspection));
     }
 
     public Path getDexPath() {
@@ -328,7 +232,7 @@
     }
 
     public String toString() {
-      return backend + " " + name + " " + kind;
+      return backend + " " + name;
     }
   }
 
@@ -341,28 +245,24 @@
           "memberrebinding",
           backend,
           TestConfiguration.AndroidVersion.PRE_N,
-          MemberRebindingTest::inspectOriginalMain,
           MemberRebindingTest::inspectMain);
       TestConfiguration.add(
           builder,
           "memberrebinding2",
           backend,
           TestConfiguration.AndroidVersion.PRE_N,
-          MemberRebindingTest::inspectOriginalMain2,
           MemberRebindingTest::inspectMain2);
       TestConfiguration.add(
           builder,
           "memberrebinding3",
           backend,
           TestConfiguration.AndroidVersion.PRE_N,
-          MemberRebindingTest::inspectOriginal3,
           MemberRebindingTest::inspect3);
       TestConfiguration.add(
           builder,
           "memberrebinding4",
           backend,
           TestConfiguration.AndroidVersion.N,
-          MemberRebindingTest::inspectOriginal4,
           MemberRebindingTest::inspect4);
     }
     return builder.build();
@@ -384,15 +284,8 @@
               .collect(Collectors.toList());
     }
 
-    if (kind == Frontend.DEX) {
-      CodeInspector inspector = new CodeInspector(originalDex);
-      originalInspection.accept(inspector);
-    }
-
     CodeInspector inspector = new CodeInspector(processed);
     inspection.accept(inspector);
 
-    // We don't run Art, as the test R8RunExamplesTest already does that.
-    // ToolHelper.checkArtOutputIdentical(originalDex, processed, mainClass, null);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 087e5d2..cda3c7d 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -37,7 +37,7 @@
 
   protected NamingTestBase(
       String test, List<String> keepRulesFiles, BiConsumer<DexItemFactory, NamingLens> inspection) {
-    appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
+    appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
     this.keepRulesFiles = keepRulesFiles;
     this.inspection = lens -> inspection.accept(dexItemFactory, lens);
   }
diff --git a/src/test/java/com/android/tools/r8/profile/art/dump/DumpArtProfileProvidersTest.java b/src/test/java/com/android/tools/r8/profile/art/dump/DumpArtProfileProvidersTest.java
new file mode 100644
index 0000000..71a58e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/dump/DumpArtProfileProvidersTest.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.profile.art.dump;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.profile.art.ArtProfileBuilder;
+import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DumpInputFlags;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.UTF8TextInputStream;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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 DumpArtProfileProvidersTest extends TestBase {
+
+  private enum DumpStrategy {
+    DIRECTORY,
+    FILE;
+
+    DumpInputFlags createDumpInputFlags(Path dump) {
+      if (this == DIRECTORY) {
+        return DumpInputFlags.dumpToDirectory(dump);
+      }
+      assert this == FILE;
+      return DumpInputFlags.dumpToFile(dump);
+    }
+
+    Path createDumpPath(TemporaryFolder temp) throws IOException {
+      if (this == DIRECTORY) {
+        return temp.newFolder().toPath();
+      }
+      assert this == FILE;
+      return temp.newFile("dump.zip").toPath();
+    }
+  }
+
+  @Parameter(0)
+  public DumpStrategy dumpStrategy;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, {0}")
+  public static List<Object[]> data() {
+    return buildParameters(DumpStrategy.values(), getTestParameters().withNoneRuntime().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path dump = dumpStrategy.createDumpPath(temp);
+    DumpInputFlags dumpInputFlags = dumpStrategy.createDumpInputFlags(dump);
+    try {
+      testForR8(Backend.DEX)
+          .addProgramClasses(Main.class)
+          .addKeepMainRule(Main.class)
+          .addOptionsModification(options -> options.setDumpInputFlags(dumpInputFlags))
+          .allowDiagnosticInfoMessages()
+          .apply(this::addArtProfileProviders)
+          .setMinApi(AndroidApiLevel.LATEST)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                if (dumpInputFlags.shouldFailCompilation()) {
+                  diagnostics.assertErrorsMatch(
+                      diagnosticMessage(containsString("Dumped compilation inputs to:")));
+                } else {
+                  diagnostics.assertInfosMatch(
+                      diagnosticMessage(containsString("Dumped compilation inputs to:")));
+                }
+              });
+      assertFalse("Expected compilation to fail", dumpInputFlags.shouldFailCompilation());
+    } catch (CompilationFailedException e) {
+      assertTrue("Expected compilation to succeed", dumpInputFlags.shouldFailCompilation());
+    }
+    verifyDump(dump);
+  }
+
+  private void addArtProfileProviders(R8FullTestBuilder testBuilder) {
+    testBuilder.addArtProfileForRewriting(
+        new ArtProfileProvider() {
+
+          @Override
+          public void getArtProfile(ArtProfileBuilder profileBuilder) {
+            profileBuilder.addHumanReadableArtProfile(
+                new UTF8TextInputStream(StringUtils.joinLines("# Comment", "Lfoo/Bar;")),
+                parserBuilder -> {});
+            ClassReference bazClassReference = Reference.classFromDescriptor("Lfoo/Baz;");
+            MethodReference bazMainMethodReference =
+                MethodReferenceUtils.mainMethod(bazClassReference);
+            profileBuilder.addClassRule(
+                classRuleBuilder -> classRuleBuilder.setClassReference(bazClassReference));
+            profileBuilder.addMethodRule(
+                methodRuleBuilder ->
+                    methodRuleBuilder
+                        .setMethodReference(bazMainMethodReference)
+                        .setMethodRuleInfo(
+                            methodRuleInfoBuilder -> methodRuleInfoBuilder.setIsHot(true)));
+          }
+
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        });
+    testBuilder.addArtProfileForRewriting(
+        new ArtProfileProvider() {
+
+          @Override
+          public void getArtProfile(ArtProfileBuilder profileBuilder) {
+            ClassReference bazClassReference = Reference.classFromDescriptor("Lfoo/Baz;");
+            MethodReference bazMainMethodReference =
+                MethodReferenceUtils.mainMethod(bazClassReference);
+            profileBuilder.addClassRule(
+                classRuleBuilder -> classRuleBuilder.setClassReference(bazClassReference));
+            profileBuilder.addHumanReadableArtProfile(
+                new UTF8TextInputStream(StringUtils.joinLines("# Comment", "Lfoo/Bar;")),
+                parserBuilder -> {});
+            profileBuilder.addMethodRule(
+                methodRuleBuilder ->
+                    methodRuleBuilder
+                        .setMethodReference(bazMainMethodReference)
+                        .setMethodRuleInfo(
+                            methodRuleInfoBuilder -> methodRuleInfoBuilder.setIsHot(true)));
+          }
+
+          @Override
+          public Origin getOrigin() {
+            return Origin.unknown();
+          }
+        });
+  }
+
+  private void verifyDump(Path dump) throws IOException {
+    if (dumpStrategy == DumpStrategy.DIRECTORY) {
+      List<Path> dumps =
+          Files.walk(dump, 1).filter(path -> path.toFile().isFile()).collect(Collectors.toList());
+      assertEquals(1, dumps.size());
+      dump = dumps.get(0);
+    }
+
+    assertTrue(Files.exists(dump));
+    Path unzipped = temp.newFolder().toPath();
+    ZipUtils.unzip(dump.toString(), unzipped.toFile());
+
+    Path artProfile1 = unzipped.resolve("art-profile-1.txt");
+    assertTrue(Files.exists(artProfile1));
+    assertEquals(
+        Lists.newArrayList(
+            "# Comment", "Lfoo/Bar;", "Lfoo/Baz;", "HLfoo/Baz;->main([Ljava/lang/String;)V"),
+        FileUtils.readAllLines(artProfile1));
+
+    Path artProfile2 = unzipped.resolve("art-profile-2.txt");
+    assertTrue(Files.exists(artProfile2));
+    assertEquals(
+        Lists.newArrayList(
+            "Lfoo/Baz;", "# Comment", "Lfoo/Bar;", "HLfoo/Baz;->main([Ljava/lang/String;)V"),
+        FileUtils.readAllLines(artProfile2));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress37740372.java b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
index 8047716..b46866c 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress37740372.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
@@ -8,12 +8,11 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DexIndexedConsumerData;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliTestBase;
@@ -22,7 +21,6 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.Base64;
-import java.util.Set;
 import org.junit.Test;
 
 public class Regress37740372 extends SmaliTestBase {
@@ -142,10 +140,9 @@
     }
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
-      assertEquals(0, fileIndex);
-      this.data = data.copyByteData();
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
+      assertEquals(0, data.getFileIndex());
+      this.data = data.getByteDataCopy();
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 0c031df..36a00f1 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -68,6 +68,7 @@
 import com.android.tools.r8.retrace.stacktraces.OutsideLineRangeStackTraceTest;
 import com.android.tools.r8.retrace.stacktraces.OverloadSameLineTest;
 import com.android.tools.r8.retrace.stacktraces.OverloadedWithAndWithoutRangeStackTrace;
+import com.android.tools.r8.retrace.stacktraces.PreambleLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
 import com.android.tools.r8.retrace.stacktraces.SingleLineNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.SourceFileNameSynthesizeStackTrace;
@@ -428,6 +429,11 @@
   }
 
   @Test
+  public void testPreambleLineNumberStackTrace() throws Exception {
+    runRetraceTest(new PreambleLineNumberStackTrace());
+  }
+
+  @Test
   public void testMapVersionWarningStackTrace() throws Exception {
     // TODO(b/204289928): Internalize the diagnostics checking.
     assumeFalse(external);
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/PreambleLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/PreambleLineNumberStackTrace.java
new file mode 100644
index 0000000..316fdfc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/PreambleLineNumberStackTrace.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class PreambleLineNumberStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "  at kotlin.t.a(SourceFile)",
+        "  at kotlin.t.a(SourceFile:0)",
+        "  at kotlin.t.a(SourceFile:1)",
+        "  at kotlin.t.a(SourceFile:2)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "kotlin.ResultKt -> kotlin.t:",
+        "  1:1:void createFailure(java.lang.Throwable):122:122 -> a",
+        "  2:2:void createFailure(java.lang.Throwable):124:124 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "  at kotlin.ResultKt.createFailure(Result.kt)",
+        // TODO(b/270593835): We should report kotlin.ResultKt.createFailure(Result.kt).
+        "  at kotlin.ResultKt.a(Result.kt:0)",
+        "  at kotlin.ResultKt.createFailure(Result.kt:122)",
+        "  at kotlin.ResultKt.createFailure(Result.kt:124)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        // TODO(b/270593835): We should not have an ambiguous frame reporting here.
+        "  at kotlin.ResultKt.void createFailure(java.lang.Throwable)(Result.kt:122)",
+        "  <OR> at kotlin.ResultKt.void createFailure(java.lang.Throwable)(Result.kt:124)",
+        // TODO(b/270593835): We should report kotlin.ResultKt.createFailure(Result.kt).
+        "  at kotlin.ResultKt.a(Result.kt:0)",
+        "  at kotlin.ResultKt.void createFailure(java.lang.Throwable)(Result.kt:122)",
+        "  at kotlin.ResultKt.void createFailure(java.lang.Throwable)(Result.kt:124)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index c67394a..3d3ed7f 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -87,6 +87,7 @@
 
               case V7_0_0:
               case V13_0_0:
+              case V14_0_0:
                 return StringUtils.joinLines(
                     "Hello!",
                     "Unexpected outcome of checkcast",
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index 63e5bc9..ef19e65 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -22,7 +22,6 @@
 
 public class R8Shaking2LookupTest {
 
-  static final String APP_FILE_NAME = ToolHelper.EXAMPLES_BUILD_DIR + "shaking2/classes.dex";
   private DirectMappedDexApplication program;
   private DexItemFactory dexItemFactory;
   private AppInfoWithClassHierarchy appInfo;
@@ -30,7 +29,9 @@
 
   @Before
   public void readApp() throws IOException, ExecutionException {
-    program = ToolHelper.buildApplication(ImmutableList.of(APP_FILE_NAME));
+    program =
+        ToolHelper.buildApplication(
+            ImmutableList.of(ToolHelper.EXAMPLES_BUILD_DIR + "shaking2.jar"));
     dexItemFactory = program.dexItemFactory;
     AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(program);
     appInfo = appView.appInfo();
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 0bc240e..223140b 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -60,10 +60,7 @@
     // Generate R8 processed version without library option.
     String test = "shaking2";
     testForR8(backend)
-        .applyIf(
-            backend.isCf(),
-            builder -> builder.addProgramFiles(getProgramFiles(test)),
-            builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+        .addProgramFiles(getProgramFiles(test))
         .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
         .addIgnoreWarnings()
         .setMinApi(minApi)
@@ -75,10 +72,7 @@
     // Generate R8 processed version without library option.
     String test = "shaking2";
     testForR8(backend)
-        .applyIf(
-            backend.isCf(),
-            builder -> builder.addProgramFiles(getProgramFiles(test)),
-            builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+        .addProgramFiles(getProgramFiles(test))
         .addLibraryFiles()
         .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
         .allowDiagnosticErrorMessages()
@@ -107,10 +101,7 @@
     // Generate R8 processed version without library option.
     String test = "shaking1";
     testForR8(backend)
-        .applyIf(
-            backend.isCf(),
-            builder -> builder.addProgramFiles(getProgramFiles(test)),
-            builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
+        .addProgramFiles(getProgramFiles(test))
         .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
         .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .setMinApi(minApi)
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index bbc5b5b..b390f85 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static org.junit.Assume.assumeFalse;
-
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
@@ -25,7 +23,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
@@ -48,36 +45,22 @@
  */
 public abstract class TreeShakingTest extends TestBase {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> defaultTreeShakingParameters() {
-    return data(Frontend.values(), MinifyMode.values());
+    return data(MinifyMode.values());
   }
 
   public static List<Object[]> data(MinifyMode[] minifyModes) {
-    return data(Frontend.values(), minifyModes);
-  }
-
-  public static List<Object[]> data(Frontend[] frontends, MinifyMode[] minifyModes) {
-    return buildParameters(
-        frontends, getTestParameters().withAllRuntimesAndApiLevels().build(), minifyModes);
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build(), minifyModes);
   }
 
   protected abstract String getName();
 
   protected abstract String getMainClass();
 
-  protected enum Frontend {
-    DEX, JAR
-  }
-
-  private final Frontend frontend;
   private final TestParameters parameters;
   private final MinifyMode minify;
 
-  public Frontend getFrontend() {
-    return frontend;
-  }
-
   public TestParameters getParameters() {
     return parameters;
   }
@@ -86,8 +69,7 @@
     return minify;
   }
 
-  public TreeShakingTest(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    this.frontend = frontend;
+  public TreeShakingTest(TestParameters parameters, MinifyMode minify) {
     this.parameters = parameters;
     this.minify = minify;
   }
@@ -173,10 +155,9 @@
       ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer,
       DiagnosticsConsumer diagnosticsConsumer)
       throws Exception {
-    assumeFalse(frontend == Frontend.DEX && parameters.isCfRuntime());
-    String originalDex = ToolHelper.TESTS_BUILD_DIR + getName() + "/classes.dex";
-    String programFile =
-        frontend == Frontend.DEX ? originalDex : ToolHelper.TESTS_BUILD_DIR + getName() + ".jar";
+
+    String programFile = ToolHelper.TESTS_BUILD_DIR + getName() + ".jar";
+
     R8FullTestBuilder testBuilder =
         testForR8(parameters.getBackend())
             // Go through app builder to add dex files.
@@ -226,36 +207,43 @@
     if (!ToolHelper.artSupported() && !ToolHelper.compareAgaintsGoldenFiles()) {
       return;
     }
-    Consumer<ArtCommandBuilder> extraArtArgs = builder -> {
-      builder.appendClasspath(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib/classes.dex");
-    };
+    Path shakingLib =
+        testForD8(Backend.DEX)
+            .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"))
+            .setMinApi(parameters)
+            .compile()
+            .writeToZip();
+    Consumer<ArtCommandBuilder> extraArtArgs =
+        builder -> {
+          builder.appendClasspath(shakingLib.toString());
+        };
+    String d8Output =
+        testForD8(Backend.DEX)
+            .setMinApi(parameters)
+            .addProgramFiles(Paths.get(programFile))
+            .compile()
+            .writeToZip()
+            .toString();
     DexVm dexVm = parameters.getRuntime().asDex().getVm();
-    if (Files.exists(Paths.get(originalDex))) {
-      if (outputComparator != null) {
-        String output1 =
-            ToolHelper.runArtNoVerificationErrors(
-                Collections.singletonList(originalDex), getMainClass(), extraArtArgs, dexVm);
-        String output2 =
-            ToolHelper.runArtNoVerificationErrors(
-                Collections.singletonList(outJar.toString()), getMainClass(), extraArtArgs, dexVm);
-        outputComparator.accept(output1, output2);
-      } else {
-        ToolHelper.checkArtOutputIdentical(
-            Collections.singletonList(originalDex),
-            Collections.singletonList(outJar.toString()),
-            getMainClass(),
-            extraArtArgs,
-            null);
-      }
-      if (dexComparator != null) {
-        CodeInspector ref = new CodeInspector(Paths.get(originalDex));
-        dexComparator.accept(ref, compileResult.inspector());
-      }
+    if (outputComparator != null) {
+      String output1 =
+          ToolHelper.runArtNoVerificationErrors(
+              Collections.singletonList(d8Output), getMainClass(), extraArtArgs, dexVm);
+      String output2 =
+          ToolHelper.runArtNoVerificationErrors(
+              Collections.singletonList(outJar.toString()), getMainClass(), extraArtArgs, dexVm);
+      outputComparator.accept(output1, output2);
     } else {
-      Assert.assertNull(outputComparator);
-      Assert.assertNull(dexComparator);
-      ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(outJar.toString()), getMainClass(), extraArtArgs, dexVm);
+      ToolHelper.checkArtOutputIdentical(
+          Collections.singletonList(d8Output),
+          Collections.singletonList(outJar.toString()),
+          getMainClass(),
+          extraArtArgs,
+          null);
+    }
+    if (dexComparator != null) {
+      CodeInspector ref = new CodeInspector(Paths.get(d8Output));
+      dexComparator.accept(ref, compileResult.inspector());
     }
     if (inspection != null) {
       compileResult.inspect(inspection);
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
index c20ebb6..a49e547 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking10Test.java
@@ -15,13 +15,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking10Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking10Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking10Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index 47b696d..9be0804 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -18,13 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking11Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking11Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking11Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index 15e18bd..c59cedc 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -24,10 +24,9 @@
 @RunWith(Parameterized.class)
 public class TreeShaking12Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return buildParameters(
-        Frontend.values(),
         getTestParameters()
             .withAllRuntimes()
             .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
@@ -35,8 +34,8 @@
         MinifyMode.values());
   }
 
-  public TreeShaking12Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking12Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 7cf7921..4e33394 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -22,13 +22,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking13Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking13Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking13Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 36b3b37..4733d3c 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
@@ -18,13 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking14Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking14Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking14Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 34da969..dcf64e5 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -23,13 +23,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking15Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return data(MinifyMode.withoutNone());
   }
 
-  public TreeShaking15Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking15Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
index 785e505..6d0136b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking16Test.java
@@ -15,13 +15,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking16Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return data(MinifyMode.withoutNone());
   }
 
-  public TreeShaking16Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking16Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index e2b7095..aa8ed3d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
@@ -19,13 +19,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking17Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking17Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking17Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index d91a2fd..eb255b8 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -18,13 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking18Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking18Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking18Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index e5f45bb..5b4b206 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -22,13 +22,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking19Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking19Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking19Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
index ad658b9..1ef98a64 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
@@ -21,13 +21,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking1Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{2}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking1Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking1Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index 74232e1..d436691 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
@@ -22,13 +22,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking2Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking2Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking2Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
index 38cee4e..57ef8e6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
@@ -18,13 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking3Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking3Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking3Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
index abf9be6..b5c6e81 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking4Test.java
@@ -15,13 +15,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking4Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking4Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking4Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
index cca8f8b..a7c07b8 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
@@ -18,13 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking5Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking5Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking5Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
index 7a91541..360394b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
@@ -19,13 +19,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking6Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking6Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking6Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
index 8a6cf36..991e93c 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
@@ -18,13 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking7Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking7Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking7Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index 9da3d01..0fe177f 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -22,13 +22,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking8Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking8Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking8Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index ec8c6ad..0b89761 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
@@ -18,13 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShaking9Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShaking9Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShaking9Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
index 0c65f06..b9ba7b5 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAndroidNTest.java
@@ -15,13 +15,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAndroidNTest extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
-    return data(new Frontend[] {Frontend.JAR}, MinifyMode.values());
+    return data(MinifyMode.values());
   }
 
-  public TreeShakingAndroidNTest(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAndroidNTest(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index 728aa09..73c2026 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -18,14 +18,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAnnotationremovalTest extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAnnotationremovalTest(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAnnotationremovalTest(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
index 1aa50e1..42c3096 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects1Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumenosideeffects1Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumenosideeffects1Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumenosideeffects1Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
index a57e98f..cb141fb 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects2Test.java
@@ -19,14 +19,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumenosideeffects2Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumenosideeffects2Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumenosideeffects2Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
index b83e0b9..ac17be8 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects3Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumenosideeffects3Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumenosideeffects3Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumenosideeffects3Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
index 56083fc..4b5a370 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects4Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumenosideeffects4Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "mode:{0}minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumenosideeffects4Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumenosideeffects4Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
index fa138dd..6fe0e05 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects5Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumenosideeffects5Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumenosideeffects5Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumenosideeffects5Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
index 172c64d..1948663 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumenosideeffects6Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumenosideeffects6Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumenosideeffects6Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumenosideeffects6Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
index 45511b7..ac1338c 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues1Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumevalues1Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues1Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumevalues1Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
index 88ca339..2dd0cc6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues2Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumevalues2Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues2Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumevalues2Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
index 5237778..f80b2f8 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues3Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumevalues3Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues3Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumevalues3Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
index 4790850..6b1d691 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues4Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumevalues4Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues4Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumevalues4Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
index 9304ed8..8ee9a97 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues5Test.java
@@ -17,14 +17,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumevalues5Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues5Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumevalues5Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
index 59c535d..882a3e4 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
@@ -20,14 +20,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumevalues6Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues6Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumevalues6Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
index c6ca232..f61dbd7 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues7Test.java
@@ -20,14 +20,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingAssumevalues7Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingAssumevalues7Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingAssumevalues7Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index 08dfc6a..4ab4fc9 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
@@ -16,13 +16,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingInliningTest extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingInliningTest(Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingInliningTest(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
index 95a0876..358de99 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMemberrebinding2Test.java
@@ -15,14 +15,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingMemberrebinding2Test extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingMemberrebinding2Test(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingMemberrebinding2Test(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
index b8151d6..1873c13 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinificationTest.java
@@ -16,14 +16,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingMinificationTest extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return defaultTreeShakingParameters();
   }
 
-  public TreeShakingMinificationTest(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingMinificationTest(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
index 7223763..6d424bd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericTest.java
@@ -15,14 +15,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingMinifygenericTest extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return data(MinifyMode.withoutNone());
   }
 
-  public TreeShakingMinifygenericTest(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingMinifygenericTest(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
index 968ef92..3763114a 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingMinifygenericwithinnerTest.java
@@ -15,14 +15,13 @@
 @RunWith(Parameterized.class)
 public class TreeShakingMinifygenericwithinnerTest extends TreeShakingTest {
 
-  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  @Parameters(name = "{0} minify:{1}")
   public static List<Object[]> data() {
     return data(MinifyMode.withoutNone());
   }
 
-  public TreeShakingMinifygenericwithinnerTest(
-      Frontend frontend, TestParameters parameters, MinifyMode minify) {
-    super(frontend, parameters, minify);
+  public TreeShakingMinifygenericwithinnerTest(TestParameters parameters, MinifyMode minify) {
+    super(parameters, minify);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java b/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java
index f4bb9ec..d9ed155 100644
--- a/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java
+++ b/src/test/java/com/android/tools/r8/startup/diagnostic/MissingStartupProfileItemsDiagnosticTest.java
@@ -46,7 +46,6 @@
         .addProgramClasses(Main.class)
         .addStartupProfileProviders(getStartupProfileProviders())
         .release()
-        .setIntermediate(true)
         .setMinApi(AndroidApiLevel.LATEST)
         .compileWithExpectedDiagnostics(this::inspectDiagnostics);
   }
diff --git a/src/test/java/com/android/tools/r8/synthesis/RepeatedCompilationNestedSyntheticsTest.java b/src/test/java/com/android/tools/r8/synthesis/RepeatedCompilationNestedSyntheticsTest.java
new file mode 100644
index 0000000..e273552
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/RepeatedCompilationNestedSyntheticsTest.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticBackportClass;
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticLambdaClass;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumerData;
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumerData;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanBox;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepeatedCompilationNestedSyntheticsTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final Backend intermediateBackend;
+
+  @Parameterized.Parameters(name = "{0}, intermediate: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withMinimumApiLevel().build(),
+        Backend.values());
+  }
+
+  public RepeatedCompilationNestedSyntheticsTest(
+      TestParameters parameters, Backend intermediateBackend) {
+    this.parameters = parameters;
+    this.intermediateBackend = intermediateBackend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals(Backend.DEX, parameters.getBackend());
+
+    ClassReference syntheticLambdaClass = syntheticLambdaClass(UsesBackport.class, 0);
+    ImmutableSet<String> expectedClassOutputs =
+        ImmutableSet.of(descriptor(UsesBackport.class), syntheticLambdaClass.getDescriptor());
+
+    Map<String, byte[]> firstCompilation = new HashMap<>();
+    testForD8(Backend.CF)
+        // High API level such that only the lambda is desugared.
+        .setMinApi(AndroidApiLevel.S)
+        .setIntermediate(true)
+        .addClasspathClasses(I.class)
+        .addProgramClasses(UsesBackport.class)
+        .setProgramConsumer(
+            new ClassFileConsumer() {
+              @Override
+              public void acceptClassFile(ClassFileConsumerData data) {
+                firstCompilation.put(data.getClassDescriptor(), data.getByteDataCopy());
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {}
+            })
+        .compile();
+    assertEquals(expectedClassOutputs, firstCompilation.keySet());
+
+    Map<String, byte[]> secondCompilation = new HashMap<>();
+    ImmutableSet.Builder<String> allDescriptors = ImmutableSet.builder();
+    BooleanBox matched = new BooleanBox(false);
+    for (Entry<String, byte[]> entry : firstCompilation.entrySet()) {
+      byte[] bytes = entry.getValue();
+      Origin origin =
+          new Origin(Origin.root()) {
+            @Override
+            public String part() {
+              return entry.getKey();
+            }
+          };
+      testForD8(intermediateBackend)
+          .setMinApi(parameters)
+          .setIntermediate(true)
+          .addClasspathClasses(I.class)
+          .apply(b -> b.getBuilder().addClassProgramData(bytes, origin))
+          .apply(
+              b ->
+                  b.getBuilder()
+                      .setDesugarGraphConsumer(
+                          new DesugarGraphConsumer() {
+
+                            @Override
+                            public void accept(Origin dependent, Origin dependency) {
+                              assertThat(
+                                  dependency.toString(), containsString(binaryName(I.class)));
+                              assertThat(
+                                  dependent.toString(),
+                                  containsString(syntheticLambdaClass.getBinaryName()));
+                              matched.set(true);
+                            }
+
+                            @Override
+                            public void finished() {}
+                          }))
+          .applyIf(
+              intermediateBackend == Backend.CF,
+              b ->
+                  b.setProgramConsumer(
+                      new ClassFileConsumer() {
+                        @Override
+                        public void acceptClassFile(ClassFileConsumerData data) {
+                          secondCompilation.put(data.getClassDescriptor(), data.getByteDataCopy());
+                          allDescriptors.add(data.getClassDescriptor());
+                        }
+
+                        @Override
+                        public void finished(DiagnosticsHandler handler) {}
+                      }),
+              b ->
+                  b.setProgramConsumer(
+                      new DexFilePerClassFileConsumer() {
+                        @Override
+                        public synchronized void acceptDexFile(
+                            DexFilePerClassFileConsumerData data) {
+                          secondCompilation.put(
+                              data.getPrimaryClassDescriptor(), data.getByteDataCopy());
+                          allDescriptors.addAll(data.getClassDescriptors());
+                        }
+
+                        @Override
+                        public void finished(DiagnosticsHandler handler) {}
+                      }))
+          .compile();
+    }
+    assertTrue(matched.get());
+    // The dex file per class file output should maintain the exact same set of primary descriptors.
+    if (intermediateBackend == Backend.DEX) {
+      assertEquals(expectedClassOutputs, secondCompilation.keySet());
+    }
+    // The total set of classes should also include the backport. The backport should be
+    // hygienically placed under the synthetic lambda (not the context of the lambda!).
+    assertEquals(
+        ImmutableSet.<String>builder()
+            .addAll(expectedClassOutputs)
+            .add(syntheticBackportClass(syntheticLambdaClass, 0).getDescriptor())
+            .build(),
+        allDescriptors.build());
+
+    testForD8(Backend.DEX)
+        .setMinApi(parameters)
+        .addProgramClasses(I.class, TestClass.class)
+        .applyIf(
+            intermediateBackend == Backend.CF,
+            b -> b.addProgramClassFileData(secondCompilation.values()),
+            b -> b.addProgramDexFileData(secondCompilation.values()))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("1")
+        .inspect(
+            inspector -> {
+              Set<String> descriptors =
+                  inspector.allClasses().stream()
+                      .map(c -> c.getFinalReference().getDescriptor())
+                      .collect(Collectors.toSet());
+              assertEquals(
+                  ImmutableSet.of(
+                      descriptor(I.class),
+                      descriptor(TestClass.class),
+                      descriptor(UsesBackport.class),
+                      // The merge step will reestablish the original contexts, thus both the lambda
+                      // and the backport are placed under the non-synthetic input class
+                      // UsesBackport.
+                      syntheticBackportClass(UsesBackport.class, 0).getDescriptor(),
+                      syntheticLambdaClass(UsesBackport.class, 1).getDescriptor()),
+                  descriptors);
+            });
+  }
+
+  interface I {
+    int compare(boolean b1, boolean b2);
+  }
+
+  static class UsesBackport {
+    public static I foo() {
+      return Boolean::compare;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(UsesBackport.foo().compare(true, false));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/RepeatedCompilationSyntheticsTest.java b/src/test/java/com/android/tools/r8/synthesis/RepeatedCompilationSyntheticsTest.java
new file mode 100644
index 0000000..842c56e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/RepeatedCompilationSyntheticsTest.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticBackportClass;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumerData;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepeatedCompilationSyntheticsTest extends TestBase {
+
+  private final String EXPECTED = StringUtils.lines("-2", "254");
+
+  private final TestParameters parameters;
+  private final Backend intermediateBackend;
+
+  private final AndroidApiLevel API_WITH_BYTE_COMPARE = AndroidApiLevel.K;
+
+  @Parameterized.Parameters(name = "{0}, intermediate: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDefaultDexRuntime().withMinimumApiLevel().build(),
+        Backend.values());
+  }
+
+  public RepeatedCompilationSyntheticsTest(TestParameters parameters, Backend intermediateBackend) {
+    this.parameters = parameters;
+    this.intermediateBackend = intermediateBackend;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getTransformedUsesBackport())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals(Backend.DEX, parameters.getBackend());
+
+    Map<String, byte[]> firstCompilation = new HashMap<>();
+    testForD8(Backend.CF)
+        // High API level such that only the compareUnsigned is desugared.
+        .setMinApi(API_WITH_BYTE_COMPARE)
+        .setIntermediate(true)
+        .addProgramClassFileData(getTransformedUsesBackport())
+        .setProgramConsumer(
+            new ClassFileConsumer() {
+              @Override
+              public synchronized void accept(
+                  ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                byte[] old = firstCompilation.put(descriptor, data.copyByteData());
+                assertNull("Duplicate " + descriptor, old);
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {}
+            })
+        .compile();
+    assertEquals(
+        ImmutableSet.of(
+            descriptor(UsesBackport.class),
+            syntheticBackportClass(UsesBackport.class, 0).getDescriptor()),
+        firstCompilation.keySet());
+
+    List<String> secondCompilation = new ArrayList<>();
+    for (Entry<String, byte[]> entry : firstCompilation.entrySet()) {
+      byte[] bytes = entry.getValue();
+      testForD8(intermediateBackend)
+          .setMinApi(parameters)
+          .setIntermediate(true)
+          .addProgramClassFileData(bytes)
+          .applyIf(
+              intermediateBackend == Backend.CF,
+              b ->
+                  b.setProgramConsumer(
+                      new ClassFileConsumer() {
+                        @Override
+                        public synchronized void accept(
+                            ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                          secondCompilation.add(descriptor);
+                        }
+
+                        @Override
+                        public void finished(DiagnosticsHandler handler) {}
+                      }),
+              b ->
+                  b.setProgramConsumer(
+                      new DexFilePerClassFileConsumer() {
+                        @Override
+                        public synchronized void acceptDexFile(
+                            DexFilePerClassFileConsumerData data) {
+                          secondCompilation.addAll(data.getClassDescriptors());
+                        }
+
+                        @Override
+                        public void finished(DiagnosticsHandler handler) {}
+                      }))
+          .compile();
+    }
+
+    // TODO(b/271235788): The repeated compilation is unsound and we have duplicate definitions of
+    //  the backports both using the same type name.
+    secondCompilation.sort(String::compareTo);
+    assertEquals(
+        ImmutableList.of(
+            syntheticBackportClass(UsesBackport.class, 0).getDescriptor(),
+            syntheticBackportClass(UsesBackport.class, 0).getDescriptor(),
+            descriptor(UsesBackport.class)),
+        secondCompilation);
+  }
+
+  private byte[] getTransformedUsesBackport() throws Exception {
+    return transformer(UsesBackport.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              assertEquals("compare", name);
+              visitor.visitMethodInsn(opcode, owner, "compareUnsigned", descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  static class UsesBackport {
+    public static int foo(byte[] bs) {
+      return Byte.compare(bs[0], bs[1]);
+    }
+
+    public static int bar(byte[] bs) {
+      return Byte.compare /*Unsigned*/(bs[0], bs[1]);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(UsesBackport.foo(new byte[] {-1, 1}));
+      System.out.println(UsesBackport.bar(new byte[] {-1, 1}));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 4321812..4190da5 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumerData;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
@@ -19,7 +19,6 @@
 import java.io.StringReader;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import org.antlr.runtime.CommonTokenStream;
@@ -129,9 +128,8 @@
     byte[] contents;
 
     @Override
-    public void accept(
-        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
-      contents = data.copyByteData();
+    public void acceptDexIndexedFile(DexIndexedConsumerData data) {
+      contents = data.getByteDataCopy();
     }
 
     @Override
diff --git a/third_party/android_jar/libcore_latest.tar.gz.sha1 b/third_party/android_jar/libcore_latest.tar.gz.sha1
index c300639..a4bef52 100644
--- a/third_party/android_jar/libcore_latest.tar.gz.sha1
+++ b/third_party/android_jar/libcore_latest.tar.gz.sha1
@@ -1 +1 @@
-f7570bec27977e786b637241778e6cfdadf25f7f
\ No newline at end of file
+a82e05e08874d8b869d7f614c24ef8981888c702
\ No newline at end of file
diff --git a/third_party/gradle/gradle-8.0.tar.gz.sha1 b/third_party/gradle/gradle-8.0.tar.gz.sha1
new file mode 100644
index 0000000..d67ee97
--- /dev/null
+++ b/third_party/gradle/gradle-8.0.tar.gz.sha1
@@ -0,0 +1 @@
+dd1f4697d211954afb3034214453c2e06769a036
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 12c8288..c313cb9 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -199,6 +199,16 @@
   def main_dex_rules_resource(self):
     return self.if_exists('main-dex-rules.txt')
 
+  def art_profile_resources(self):
+    art_profile_resources = []
+    while True:
+      current_art_profile_index = len(art_profile_resources) + 1
+      art_profile_resource = self.if_exists(
+          'art-profile-%s.txt' % current_art_profile_index)
+      if art_profile_resource is None:
+        return art_profile_resources
+      art_profile_resources.append(art_profile_resource)
+
   def startup_profile_resources(self):
     startup_profile_resources = []
     while True:
@@ -252,6 +262,8 @@
         if stripped:
           pair = stripped.split('=')
           build_properties[pair[0]] = pair[1]
+    if 'mode' not in build_properties:
+      build_properties['mode'] = 'release'
   return build_properties
 
 def determine_version(args, dump):
@@ -260,21 +272,33 @@
   return args.version
 
 def determine_compiler(args, build_properties):
-  compilers = ['d8', 'r8', 'r8full', 'l8', 'l8d8']
+  compilers = ['d8', 'r8', 'r8full', 'l8', 'l8d8', 'tracereferences']
   compiler = args.compiler
   if not compiler and 'tool' in build_properties:
     compiler = build_properties.get('tool').lower()
-    if (compiler == 'r8'):
+    if compiler == 'r8':
       if not 'force-proguard-compatibility' in build_properties:
         error("Unable to determine R8 compiler variant from build.properties."
               " No value for 'force-proguard-compatibility'.")
       if build_properties.get('force-proguard-compatibility').lower() == 'false':
         compiler = compiler + 'full'
+    if compiler == 'TraceReferences':
+      compiler = build_properties.get('tool').lower()
   if compiler not in compilers:
     error("Unable to determine a compiler to use. Specified %s,"
           " Valid options: %s" % (args.compiler, ', '.join(compilers)))
   return compiler
 
+def determine_trace_references_commands(build_properties, output):
+  trace_ref_consumer = build_properties.get('trace_references_consumer')
+  if trace_ref_consumer == 'com.android.tools.r8.tracereferences.TraceReferencesCheckConsumer':
+    return ["--check"]
+  else:
+    assert trace_ref_consumer == 'com.android.tools.r8.tracereferences.TraceReferencesKeepRules'
+    args = ['--allowobfuscation'] if build_properties.get('minification') == 'true' else []
+    args.extend(['--keep-rules', '--output', output])
+    return args
+
 def is_l8_compiler(compiler):
   return compiler.startswith('l8')
 
@@ -304,6 +328,9 @@
     return build_properties.get('min-api')
   return None
 
+def determine_residual_art_profile_output(art_profile, temp):
+  return os.path.join(temp, os.path.basename(art_profile)[:-4] + ".out.txt")
+
 def determine_desugared_lib_pg_conf_output(temp):
   return os.path.join(temp, 'desugared-library-keep-rules.config')
 
@@ -400,7 +427,7 @@
   return False
 
 def prepare_r8_wrapper(dist, temp, jdkhome):
-  compile_with_javac(
+  compile_wrapper_with_javac(
     dist,
     temp,
     jdkhome,
@@ -409,7 +436,7 @@
       'src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java'))
 
 def prepare_d8_wrapper(dist, temp, jdkhome):
-  compile_with_javac(
+  compile_wrapper_with_javac(
     dist,
     temp,
     jdkhome,
@@ -417,10 +444,14 @@
       utils.REPO_ROOT,
       'src/main/java/com/android/tools/r8/utils/CompileDumpD8.java'))
 
-def compile_with_javac(dist, temp, jdkhome, path):
+def compile_wrapper_with_javac(dist, temp, jdkhome, path):
+  base_path = os.path.join(
+      utils.REPO_ROOT,
+      'src/main/java/com/android/tools/r8/utils/CompileDumpBase.java')
   cmd = [
     jdk.GetJavacExecutable(jdkhome),
     path,
+    base_path,
     '-d', temp,
     '-cp', dist,
   ]
@@ -478,7 +509,7 @@
     if args.r8_flags:
       cmd.extend(args.r8_flags.split(' '))
     if hasattr(args, 'properties'):
-      cmd.extend(args.properties);
+      cmd.extend(args.properties)
     cmd.extend(determine_properties(build_properties))
     cmd.extend(['-cp', '%s:%s' % (temp, jar)])
     if compiler == 'd8':
@@ -486,25 +517,33 @@
       cmd.append('com.android.tools.r8.utils.CompileDumpD8')
     if is_l8_compiler(compiler):
       cmd.append('com.android.tools.r8.L8')
+    if compiler == 'tracereferences':
+      cmd.append('com.android.tools.r8.tracereferences.TraceReferences')
+      cmd.extend(determine_trace_references_commands(build_properties, out))
     if compiler.startswith('r8'):
       prepare_r8_wrapper(jar, temp, jdkhome)
       cmd.append('com.android.tools.r8.utils.CompileDumpCompatR8')
     if compiler == 'r8':
       cmd.append('--compat')
-    if mode == 'debug':
-      cmd.append('--debug')
-    else:
-      cmd.append('--release')
+    if compiler != 'tracereferences':
+      assert mode == 'debug' or mode == 'release'
+      cmd.append('--' + mode)
     # For recompilation of dumps run_on_app_dumps pass in a program jar.
-    cmd.append(determine_program_jar(args, dump))
-    cmd.extend(['--output', out])
+    program_jar = determine_program_jar(args, dump)
+    if compiler != 'tracereferences':
+      cmd.append(program_jar)
+      cmd.extend(['--output', out])
+    else:
+      cmd.extend(['--source', program_jar])
     for feature_jar in dump.feature_jars():
       cmd.extend(['--feature-jar', feature_jar,
                  determine_feature_output(feature_jar, temp)])
     if dump.library_jar():
       cmd.extend(['--lib', dump.library_jar()])
     if dump.classpath_jar() and not is_l8_compiler(compiler):
-      cmd.extend(['--classpath', dump.classpath_jar()])
+      cmd.extend(
+        ['--target' if compiler == 'tracereferences' else '--classpath',
+         dump.classpath_jar()])
     if dump.desugared_library_json() and not args.disable_desugared_lib:
       cmd.extend(['--desugared-lib', dump.desugared_library_json()])
       if not is_l8_compiler(compiler):
@@ -525,6 +564,11 @@
       cmd.extend(['--main-dex-list', dump.main_dex_list_resource()])
     if dump.main_dex_rules_resource():
       cmd.extend(['--main-dex-rules', dump.main_dex_rules_resource()])
+    for art_profile_resource in dump.art_profile_resources():
+      residual_art_profile_output = \
+          determine_residual_art_profile_output(art_profile_resource, temp)
+      cmd.extend([
+          '--art-profile', art_profile_resource, residual_art_profile_output])
     for startup_profile_resource in dump.startup_profile_resources():
       cmd.extend(['--startup-profile', startup_profile_resource])
     if min_api:
diff --git a/tools/gradle.py b/tools/gradle.py
index 92503a7..5e08468 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -17,18 +17,25 @@
 
 GRADLE_DIR = os.path.join(utils.REPO_ROOT, 'third_party', 'gradle')
 GRADLE_SHA1 = os.path.join(GRADLE_DIR, 'gradle.tar.gz.sha1')
+GRADLE8_SHA1 = os.path.join(GRADLE_DIR, 'gradle-8.0.tar.gz.sha1')
 GRADLE_TGZ = os.path.join(GRADLE_DIR, 'gradle.tar.gz')
+GRADLE8_TGZ = os.path.join(GRADLE_DIR, 'gradle-8.0.tar.gz')
 
-if utils.IsWindows():
-  GRADLE = os.path.join(GRADLE_DIR, 'gradle', 'bin', 'gradle.bat')
-else:
-  GRADLE = os.path.join(GRADLE_DIR, 'gradle', 'bin', 'gradle')
+def get_gradle(new_gradle):
+  gradle_dir = 'gradle-8.0' if new_gradle else 'gradle'
+  if utils.IsWindows():
+    return os.path.join(GRADLE_DIR, gradle_dir, 'bin', 'gradle.bat')
+  else:
+    return os.path.join(GRADLE_DIR, gradle_dir, 'bin', 'gradle')
 
 def ParseOptions():
   parser = argparse.ArgumentParser(description = 'Call gradle.')
   parser.add_argument('--exclude-deps', '--exclude_deps',
       help='Build without internalized dependencies.',
       default=False, action='store_true')
+  parser.add_argument('--new-gradle', '--new_gradle',
+                      help='Run with new experimental gradle configuration',
+                      default=False, action='store_true')
   parser.add_argument('--no-internal', '--no_internal',
       help='Do not build with support for Google internal tests.',
       default=False, action='store_true')
@@ -56,7 +63,9 @@
 
 def EnsureGradle():
   utils.EnsureDepFromGoogleCloudStorage(
-    GRADLE, GRADLE_TGZ, GRADLE_SHA1, 'Gradle binary')
+    get_gradle(False), GRADLE_TGZ, GRADLE_SHA1, 'Gradle binary')
+  utils.EnsureDepFromGoogleCloudStorage(
+    get_gradle(True), GRADLE8_TGZ, GRADLE8_SHA1, 'Gradle binary')
 
 def EnsureJdk():
   jdkRoot = jdk.GetJdkRoot()
@@ -68,9 +77,12 @@
   EnsureGradle()
   EnsureJdk()
 
-def RunGradleIn(gradleCmd, args, cwd, throw_on_failure=True, env=None):
+def RunGradleIn(
+    gradleCmd, args, cwd, throw_on_failure=True, env=None, new_gradle=False):
   EnsureDeps()
   cmd = [gradleCmd]
+  args.append(
+    '-c=d8_r8/settings.gradle.kts' if new_gradle else '-b=build.gradle')
   cmd.extend(args)
   utils.PrintCmd(cmd)
   with utils.ChangedWorkingDirectory(cwd):
@@ -82,8 +94,14 @@
 def RunGradleWrapperIn(args, cwd, throw_on_failure=True, env=None):
   return RunGradleIn('./gradlew', args, cwd, throw_on_failure, env=env)
 
-def RunGradle(args, throw_on_failure=True, env=None):
-  return RunGradleIn(GRADLE, args, utils.REPO_ROOT, throw_on_failure, env=env)
+def RunGradle(args, throw_on_failure=True, env=None, new_gradle=False):
+  return RunGradleIn(
+    get_gradle(new_gradle),
+    args,
+    utils.REPO_ROOT,
+    throw_on_failure,
+    env=env,
+    new_gradle=new_gradle)
 
 def RunGradleExcludeDeps(args, throw_on_failure=True, env=None):
   EnsureDeps()
@@ -101,8 +119,12 @@
 def RunGradleWrapperInGetOutput(args, cwd, env=None):
   return RunGradleInGetOutput('./gradlew', args, cwd, env=env)
 
-def RunGradleGetOutput(args, env=None):
-  return RunGradleInGetOutput(GRADLE, args, utils.REPO_ROOT, env=env)
+def RunGradleGetOutput(args, env=None, new_gradle=False):
+  return RunGradleInGetOutput(
+    get_gradle(new_gradle),
+    args,
+    utils.REPO_ROOT,
+    env=env)
 
 def Main():
   (options, args) = ParseOptions()
@@ -114,7 +136,7 @@
     args.append('-Pexclude_deps')
   if options.worktree:
     args.append('-g=' + os.path.join(utils.REPO_ROOT, ".gradle_user_home"))
-  return RunGradle(args)
+  return RunGradle(args, new_gradle=options.new_gradle)
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index 611c06e..03ddaa3 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -65,6 +65,37 @@
 
 (cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-master)
 
+repo init -b udc-preview1-release
+
+art-14 (Android U)
+------------------
+Build branch udc-preview1-release.
+
+export BRANCH=udc-preview1-release
+mkdir ${BRANCH}
+cd ${BRANCH}
+export ANDROID_CHECKOUT=$(pwd)
+repo init -u https://android.googlesource.com/platform/manifest -b ${BRANCH}
+repo sync -cq -j48
+source build/envsetup.sh
+lunch aosp_redfin-userdebug
+m -j48
+m -j48 build-art
+m -j48 test-art-host
+repo manifest -r -o build_spec.xml
+
+Collected into tools/linux/host/art-14.0.0-dp1. The "host" path element is checked
+by the script for running Art.
+
+  cd <r8 checkout>
+  scripts/update-host-art.sh \
+     --android-checkout $ANDROID_CHECKOUT \
+     --art-dir host/art-14.0.0-dp1 \
+     --android-product redfin
+
+  (cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-14.0.0-dp1)
+
+
 art-13 (Android T)
 ------------------
 Build branch android-13.0.0_r3.
@@ -86,10 +117,10 @@
   cd <r8 checkout>
   scripts/update-host-art.sh \
      --android-checkout <...>/android-13.0.0_r3 \
-     --art-dir host/art-13 \
+     --art-dir host/art-13.0.0 \
      --android-product redfin
 
-  (cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-13)
+  (cd tools/linux/host; upload_to_google_storage.py -a --bucket r8-deps art-13.0.0)
 
 art-12.0.0 (Android S)
 ---------------------
diff --git a/tools/linux/host/art-14.0.0-dp1.tar.gz.sha1 b/tools/linux/host/art-14.0.0-dp1.tar.gz.sha1
new file mode 100644
index 0000000..c88b923
--- /dev/null
+++ b/tools/linux/host/art-14.0.0-dp1.tar.gz.sha1
@@ -0,0 +1 @@
+1c41f5d79e335ff132b7a95d995b65581c250df8
\ No newline at end of file
diff --git a/tools/test.py b/tools/test.py
index 72b2962..2f0254c 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -28,6 +28,7 @@
 
 ALL_ART_VMS = [
     "default",
+    "14.0.0",
     "13.0.0",
     "12.0.0",
     "10.0.0",