Merge commit '5b37636b77ffdb29b8314921071f842a566a3b54' into dev-release

Change-Id: Id2b9a5a47f2d47f16a4eed0cc2fcf29885627ea8
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index e809804..25d24e38 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -388,6 +388,7 @@
 object JvmCompatibility {
   val sourceCompatibility = JavaVersion.VERSION_11
   val targetCompatibility = JavaVersion.VERSION_11
+  val release = 11
 }
 
 object Versions {
@@ -398,7 +399,7 @@
   const val guavaVersion = "32.1.2-jre"
   const val javassist = "3.29.2-GA"
   const val junitVersion = "4.13-beta-2"
-  const val kotlinVersion = "1.9.0"
+  const val kotlinVersion = "1.9.20"
   const val kotlinMetadataVersion = "2.0.0-Beta5"
   const val mockito = "2.10.0"
   const val smaliVersion = "3.0.3"
diff --git a/d8_r8/gradle.properties b/d8_r8/gradle.properties
index a82d85e..473e9a7 100644
--- a/d8_r8/gradle.properties
+++ b/d8_r8/gradle.properties
@@ -16,3 +16,28 @@
 # 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
+# Configure Java toolchains
+#
+# Would have liked to include third_party/openjdk/jdk8/linux-x86, but Gradle
+# does not fully recognize it as a JDK-8.
+#
+# Run
+#   tools/gradle.py -q javaToolchains
+# to check the actual JDK's picked up by Gradle.
+#
+# NOTE: Gradle will ignore directories which does not exist, as will be the
+# case for JDK's for platforms different from the one this is running on
+# when using the default downloadDeps setup.
+org.gradle.java.installations.paths=\
+../third_party/openjdk/jdk-11/linux,\
+../third_party/openjdk/jdk-17/linux,\
+../third_party/openjdk/jdk-21/linux,\
+../third_party/openjdk/jdk-22/linux,\
+../third_party/openjdk/jdk-11/osx,\
+../third_party/openjdk/jdk-17/osx,\
+../third_party/openjdk/jdk-21/osx,\
+../third_party/openjdk/jdk-22/osx,\
+../third_party/openjdk/jdk-11/windows,\
+../third_party/openjdk/jdk-17/windows,\
+../third_party/openjdk/jdk-21/windows,\
+../third_party/openjdk/jdk-22/windows
diff --git a/d8_r8/keepanno/build.gradle.kts b/d8_r8/keepanno/build.gradle.kts
index 3d981f6..e58f672 100644
--- a/d8_r8/keepanno/build.gradle.kts
+++ b/d8_r8/keepanno/build.gradle.kts
@@ -13,6 +13,9 @@
   }
   sourceCompatibility = JvmCompatibility.sourceCompatibility
   targetCompatibility = JvmCompatibility.targetCompatibility
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
   withSourcesJar()
 }
 
diff --git a/d8_r8/library_desugar/build.gradle.kts b/d8_r8/library_desugar/build.gradle.kts
index 4094e64..0211cb6 100644
--- a/d8_r8/library_desugar/build.gradle.kts
+++ b/d8_r8/library_desugar/build.gradle.kts
@@ -14,6 +14,9 @@
   }
   sourceCompatibility = JavaVersion.VERSION_1_8
   targetCompatibility = JavaVersion.VERSION_1_8
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
 }
 
 dependencies {
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts
index f365f9f..9eaf601 100644
--- a/d8_r8/main/build.gradle.kts
+++ b/d8_r8/main/build.gradle.kts
@@ -31,6 +31,9 @@
   }
   sourceCompatibility = JvmCompatibility.sourceCompatibility
   targetCompatibility = JvmCompatibility.targetCompatibility
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
   withSourcesJar()
 }
 
diff --git a/d8_r8/resourceshrinker/build.gradle.kts b/d8_r8/resourceshrinker/build.gradle.kts
index 183d1ed..db14c2f 100644
--- a/d8_r8/resourceshrinker/build.gradle.kts
+++ b/d8_r8/resourceshrinker/build.gradle.kts
@@ -16,6 +16,9 @@
   }
   sourceCompatibility = JvmCompatibility.sourceCompatibility
   targetCompatibility = JvmCompatibility.targetCompatibility
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
   withSourcesJar()
 }
 
@@ -47,7 +50,7 @@
     kotlinOptions {
       // 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.
-      jvmTarget = "11"
+      jvmTarget = "${JvmCompatibility.release}"
     }
   }
 
diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts
index 14b066a..7b2de4e 100644
--- a/d8_r8/test/build.gradle.kts
+++ b/d8_r8/test/build.gradle.kts
@@ -12,8 +12,11 @@
 }
 
 java {
-  sourceCompatibility = JavaVersion.VERSION_17
-  targetCompatibility = JavaVersion.VERSION_17
+  sourceCompatibility = JvmCompatibility.sourceCompatibility
+  targetCompatibility = JvmCompatibility.targetCompatibility
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
 }
 
 dependencies { }
diff --git a/d8_r8/test_modules/testbase/build.gradle.kts b/d8_r8/test_modules/testbase/build.gradle.kts
index 2d66a1c..336033b 100644
--- a/d8_r8/test_modules/testbase/build.gradle.kts
+++ b/d8_r8/test_modules/testbase/build.gradle.kts
@@ -24,6 +24,9 @@
   // compatible with java toolchains.
   sourceCompatibility = JavaVersion.VERSION_1_8
   targetCompatibility = JavaVersion.VERSION_1_8
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
 }
 
 // If we depend on keepanno by referencing the project source outputs we get an error regarding
diff --git a/d8_r8/test_modules/tests_bootstrap/build.gradle.kts b/d8_r8/test_modules/tests_bootstrap/build.gradle.kts
index f6120cc..7b900dc 100644
--- a/d8_r8/test_modules/tests_bootstrap/build.gradle.kts
+++ b/d8_r8/test_modules/tests_bootstrap/build.gradle.kts
@@ -23,6 +23,9 @@
   // compatible with java toolchains.
   sourceCompatibility = JavaVersion.VERSION_1_8
   targetCompatibility = JavaVersion.VERSION_1_8
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
 }
 
 val testbaseJavaCompileTask = projectTask("testbase", "compileJava")
diff --git a/d8_r8/test_modules/tests_java_11/build.gradle.kts b/d8_r8/test_modules/tests_java_11/build.gradle.kts
index 3c99b88..f0748b2 100644
--- a/d8_r8/test_modules/tests_java_11/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_11/build.gradle.kts
@@ -29,6 +29,7 @@
   implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile()))
   implementation(testbaseJavaCompileTask.outputs.files)
   implementation(mainCompileTask.outputs.files)
+  implementation(projectTask("main", "processResources").outputs.files)
 }
 
 // We just need to register the examples jars for it to be referenced by other modules.
diff --git a/d8_r8/test_modules/tests_java_17/build.gradle.kts b/d8_r8/test_modules/tests_java_17/build.gradle.kts
index 2079c40..d46f487 100644
--- a/d8_r8/test_modules/tests_java_17/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_17/build.gradle.kts
@@ -32,6 +32,7 @@
   implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile()))
   implementation(testbaseJavaCompileTask.outputs.files)
   implementation(mainCompileTask.outputs.files)
+  implementation(projectTask("main", "processResources").outputs.files)
 }
 
 // We just need to register the examples jars for it to be referenced by other modules.
diff --git a/d8_r8/test_modules/tests_java_21/build.gradle.kts b/d8_r8/test_modules/tests_java_21/build.gradle.kts
index afc55d0..403eb85 100644
--- a/d8_r8/test_modules/tests_java_21/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_21/build.gradle.kts
@@ -28,6 +28,7 @@
   implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile()))
   implementation(testbaseJavaCompileTask.outputs.files)
   implementation(mainCompileTask.outputs.files)
+  implementation(projectTask("main", "processResources").outputs.files)
 }
 
 // We just need to register the examples jars for it to be referenced by other modules.
diff --git a/d8_r8/test_modules/tests_java_22/build.gradle.kts b/d8_r8/test_modules/tests_java_22/build.gradle.kts
index 546d795..938e1cb 100644
--- a/d8_r8/test_modules/tests_java_22/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_22/build.gradle.kts
@@ -28,6 +28,7 @@
   implementation(files(testbaseDepsJarTask.outputs.files.getSingleFile()))
   implementation(testbaseJavaCompileTask.outputs.files)
   implementation(mainCompileTask.outputs.files)
+  implementation(projectTask("main", "processResources").outputs.files)
 }
 
 tasks {
diff --git a/d8_r8/test_modules/tests_java_8/build.gradle.kts b/d8_r8/test_modules/tests_java_8/build.gradle.kts
index 779cd6c..31914df 100644
--- a/d8_r8/test_modules/tests_java_8/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_8/build.gradle.kts
@@ -21,11 +21,13 @@
       srcDir(root.resolveAll("build", "generated", "test", "java"))
     }
   }
-
   // We are using a new JDK to compile to an older language version, which is not directly
   // compatible with java toolchains.
   sourceCompatibility = JavaVersion.VERSION_1_8
   targetCompatibility = JavaVersion.VERSION_1_8
+  toolchain {
+    languageVersion = JavaLanguageVersion.of(JvmCompatibility.release)
+  }
 }
 
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
index cfd4302..0327246 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
@@ -69,7 +69,7 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern namePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern namePattern() default @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Specify which retention policies must be set for the annotations.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
index 58b9fe8..3c9e261 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
@@ -29,8 +29,8 @@
    *
    * <ul>
    *   <li>constant
-   *   <li>simpleName
-   *   <li>simpleNamePattern
+   *   <li>unqualifiedName
+   *   <li>unqualifiedNamePattern
    *   <li>packageName
    * </ul>
    *
@@ -45,8 +45,8 @@
    *
    * <ul>
    *   <li>name
-   *   <li>simpleName
-   *   <li>simpleNamePattern
+   *   <li>unqualifiedName
+   *   <li>unqualifiedNamePattern
    *   <li>packageName
    * </ul>
    *
@@ -55,38 +55,40 @@
   Class<?> constant() default Object.class;
 
   /**
-   * Exact simple name of the class or interface.
+   * Exact and unqualified name of the class or interface.
    *
-   * <p>For example, the simple name of {@code com.example.MyClass} is {@code MyClass}.
+   * <p>For example, the unqualified name of {@code com.example.MyClass} is {@code MyClass}. Note
+   * that for inner classes a `$` will appear in the unqualified name,such as, {@code
+   * MyClass$MyInnerClass}.
    *
-   * <p>The default matches any simple name.
+   * <p>The default matches any unqualified name.
    *
-   * <p>Mutually exclusive with the following other properties defining class-simple-name:
+   * <p>Mutually exclusive with the following other properties defining class-unqualified-name:
    *
    * <ul>
-   *   <li>simpleNamePattern
+   *   <li>unqualifiedNamePattern
    *   <li>name
    *   <li>constant
    * </ul>
    */
-  String simpleName() default "";
+  String unqualifiedName() default "";
 
   /**
-   * Define the simple-name pattern by a string pattern.
+   * Define the unqualified class-name pattern by a string pattern.
    *
-   * <p>The default matches any simple name.
+   * <p>The default matches any unqualified name.
    *
-   * <p>Mutually exclusive with the following other properties defining class-simple-name:
+   * <p>Mutually exclusive with the following other properties defining class-unqualified-name:
    *
    * <ul>
-   *   <li>simpleName
+   *   <li>unqualifiedName
    *   <li>name
    *   <li>constant
    * </ul>
    *
-   * @return The string pattern of the class simple name.
+   * @return The string pattern of the unqualified class name.
    */
-  StringPattern simpleNamePattern() default @StringPattern(exact = "");
+  StringPattern unqualifiedNamePattern() default @StringPattern(exact = "");
 
   /**
    * Exact package name of the class or interface.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java
index 591d750..975f144 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/InstanceOfPattern.java
@@ -31,5 +31,5 @@
   boolean inclusive() default true;
 
   /** Instances of classes matching the class-name pattern. */
-  ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = "");
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
index f92ab49..68adfaa 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -140,7 +140,7 @@
    *
    * @return The class-name pattern that defines the class.
    */
-  ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the instance-of pattern as classes that are instances of the fully qualified class name.
@@ -295,7 +295,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member-annotated-by pattern by fully qualified class name.
@@ -355,7 +356,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern memberAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member-access pattern by matching on access flags.
@@ -422,7 +424,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern methodAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the method-access pattern by matching on access flags.
@@ -604,7 +607,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern fieldAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 65d13d8..90b641e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -102,7 +102,7 @@
    *
    * @return The class-name pattern that defines the class.
    */
-  ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the instance-of pattern as classes that are instances of the fully qualified class name.
@@ -257,7 +257,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member pattern in full by a reference to a binding.
@@ -330,7 +331,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern memberAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member-access pattern by matching on access flags.
@@ -402,7 +404,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern methodAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the method-access pattern by matching on access flags.
@@ -612,7 +615,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern fieldAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
index aac3246..6a06f60 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepForApi.java
@@ -115,7 +115,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern memberAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member-access pattern by matching on access flags.
@@ -182,7 +183,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern methodAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the method-access pattern by matching on access flags.
@@ -364,7 +366,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern fieldAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index e7e8c74..04f1aac 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -199,7 +199,7 @@
    *
    * @return The class-name pattern that defines the class.
    */
-  ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the instance-of pattern as classes that are instances of the fully qualified class name.
@@ -354,7 +354,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern classAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member pattern in full by a reference to a binding.
@@ -427,7 +428,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern memberAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member-access pattern by matching on access flags.
@@ -499,7 +501,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern methodAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the method-access pattern by matching on access flags.
@@ -709,7 +712,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern fieldAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
index 2fa0ffd..47fdb1d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
@@ -62,5 +62,5 @@
    *   <li>constant
    * </ul>
    */
-  ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern classNamePattern() default @ClassNamePattern(unqualifiedName = "");
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
index 076a93c..4bf6253 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -205,7 +205,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern memberAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member-access pattern by matching on access flags.
@@ -272,7 +273,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern methodAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the method-access pattern by matching on access flags.
@@ -454,7 +456,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern fieldAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
index cf2225d..4a0c692 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -205,7 +205,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern memberAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern memberAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the member-access pattern by matching on access flags.
@@ -272,7 +273,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern methodAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern methodAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the method-access pattern by matching on access flags.
@@ -454,7 +456,8 @@
    *
    * @return The class-name pattern that defines the annotation.
    */
-  ClassNamePattern fieldAnnotatedByClassNamePattern() default @ClassNamePattern(simpleName = "");
+  ClassNamePattern fieldAnnotatedByClassNamePattern() default
+      @ClassNamePattern(unqualifiedName = "");
 
   /**
    * Define the field-access pattern by matching on access flags.
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index e066873..538c8ca 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.keepanno.asm;
 
 import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
-import com.android.tools.r8.keepanno.asm.ClassSimpleNameParser.ClassSimpleNameProperty;
+import com.android.tools.r8.keepanno.asm.ClassUnqualifiedNameParser.ClassUnqualifiedNameProperty;
 import com.android.tools.r8.keepanno.asm.PackageNameParser.PackageNameProperty;
 import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
 import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
@@ -85,23 +85,27 @@
               getParsingContext().property(name).annotation(descriptor);
           ClassNameParser fullNameParser = new ClassNameParser(parsingContext);
           PackageNameParser packageParser = new PackageNameParser(parsingContext);
-          ClassSimpleNameParser simpleNameParser = new ClassSimpleNameParser(parsingContext);
+          ClassUnqualifiedNameParser unqualifiedNameParser =
+              new ClassUnqualifiedNameParser(parsingContext);
           fullNameParser.setProperty(ClassNamePattern.name, ClassNameProperty.NAME);
           fullNameParser.setProperty(ClassNamePattern.constant, ClassNameProperty.CONSTANT);
           packageParser.setProperty(ClassNamePattern.packageName, PackageNameProperty.NAME);
-          simpleNameParser.setProperty(ClassNamePattern.simpleName, ClassSimpleNameProperty.NAME);
-          simpleNameParser.setProperty(
-              ClassNamePattern.simpleNamePattern, ClassSimpleNameProperty.PATTERN);
+          unqualifiedNameParser.setProperty(
+              ClassNamePattern.unqualifiedName, ClassUnqualifiedNameProperty.NAME);
+          unqualifiedNameParser.setProperty(
+              ClassNamePattern.unqualifiedNamePattern, ClassUnqualifiedNameProperty.PATTERN);
 
           return new ParserVisitor(
               parsingContext,
-              ImmutableList.of(fullNameParser, packageParser, simpleNameParser),
+              ImmutableList.of(fullNameParser, packageParser, unqualifiedNameParser),
               () -> {
                 if (fullNameParser.isDeclared()) {
-                  if (simpleNameParser.isDeclared() || packageParser.isDeclared()) {
+                  if (unqualifiedNameParser.isDeclared() || packageParser.isDeclared()) {
                     throw parsingContext.error(
                         "Cannot specify both the full class name and its "
-                            + (simpleNameParser.isDeclared() ? "simple name" : "package"));
+                            + (unqualifiedNameParser.isDeclared()
+                                ? "unqualified name"
+                                : "package"));
                   }
                   setValue.accept(fullNameParser.getValue());
                   return;
@@ -111,7 +115,7 @@
                         .setPackagePattern(
                             packageParser.getValueOrDefault(KeepPackagePattern.any()))
                         .setNamePattern(
-                            simpleNameParser.getValueOrDefault(
+                            unqualifiedNameParser.getValueOrDefault(
                                 KeepUnqualfiedClassNamePattern.any()))
                         .build());
               });
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassUnqualifiedNameParser.java
similarity index 79%
rename from src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java
rename to src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassUnqualifiedNameParser.java
index d65aa22..896fea9 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassUnqualifiedNameParser.java
@@ -1,34 +1,34 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.keepanno.asm;
 
-import com.android.tools.r8.keepanno.asm.ClassSimpleNameParser.ClassSimpleNameProperty;
+import com.android.tools.r8.keepanno.asm.ClassUnqualifiedNameParser.ClassUnqualifiedNameProperty;
 import com.android.tools.r8.keepanno.asm.StringPatternParser.StringProperty;
 import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.ParsingContext;
 import java.util.function.Consumer;
 import org.objectweb.asm.AnnotationVisitor;
 
-public class ClassSimpleNameParser
-    extends PropertyParserBase<KeepUnqualfiedClassNamePattern, ClassSimpleNameProperty> {
+public class ClassUnqualifiedNameParser
+    extends PropertyParserBase<KeepUnqualfiedClassNamePattern, ClassUnqualifiedNameProperty> {
 
   private final StringPatternParser parser;
 
-  public ClassSimpleNameParser(ParsingContext parsingContext) {
+  public ClassUnqualifiedNameParser(ParsingContext parsingContext) {
     super(parsingContext);
     parser = new StringPatternParser(parsingContext);
   }
 
-  public enum ClassSimpleNameProperty {
+  public enum ClassUnqualifiedNameProperty {
     NAME,
     PATTERN
   }
 
   @Override
   public boolean tryProperty(
-      ClassSimpleNameProperty property,
+      ClassUnqualifiedNameProperty property,
       String name,
       Object value,
       Consumer<KeepUnqualfiedClassNamePattern> setValue) {
@@ -47,7 +47,7 @@
 
   @Override
   AnnotationVisitor tryPropertyAnnotation(
-      ClassSimpleNameProperty property,
+      ClassUnqualifiedNameProperty property,
       String name,
       String descriptor,
       Consumer<KeepUnqualfiedClassNamePattern> setValue) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 07789a5..9151b1b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -503,7 +503,7 @@
             v.visit(ClassNamePattern.packageName, packagePattern.getExactPackageAsString());
           }
           writeStringPattern(
-              clazz.getNamePattern().asStringPattern(), ClassNamePattern.simpleNamePattern, v);
+              clazz.getNamePattern().asStringPattern(), ClassNamePattern.unqualifiedNamePattern, v);
         });
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index f529bd1..9b0ae29 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -245,9 +245,9 @@
     public static final String classNameGroup = "class-name";
     public static final String name = "name";
     public static final String constant = "constant";
-    public static final String classSimpleNameGroup = "class-simple-name";
-    public static final String simpleName = "simpleName";
-    public static final String simpleNamePattern = "simpleNamePattern";
+    public static final String classUnqualifiedNameGroup = "class-unqualified-name";
+    public static final String unqualifiedName = "unqualifiedName";
+    public static final String unqualifiedNamePattern = "unqualifiedNamePattern";
     public static final String packageName = "packageName";
   }
 
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 9932654..e2ce313 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -81,7 +81,7 @@
  *     ::= any
  *       | PACKAGE_PATTERN UNQUALIFIED_CLASS_NAME_PATTERN
  *
- *   UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name
+ *   UNQUALIFIED_CLASS_NAME_PATTERN ::= STRING_PATTERN
  *
  *   INSTANCE_OF_PATTERN ::= INSTANCE_OF_PATTERN_INCLUSIVE | INSTANCE_OF_PATTERN_EXCLUSIVE
  *   INSTANCE_OF_PATTERN_INCLUSIVE ::= QUALIFIED_CLASS_NAME_PATTERN
@@ -128,6 +128,10 @@
  *
  *   RETENTION_POLICY
  *     ::= RUNTIME | CLASS
+ *
+ *   STRING_PATTERN
+ *     ::= exact string-content
+ *       | [prefix string-content] any [suffix string-content]
  * </pre>
  */
 public final class KeepEdge extends KeepDeclaration {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
index abb24be..a974f67 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -24,26 +24,26 @@
     return new Builder();
   }
 
-  private final KeepStringPattern simpleNamePattern;
+  private final KeepStringPattern unqualifiedNamePattern;
 
-  private KeepUnqualfiedClassNamePattern(KeepStringPattern simpleNamePattern) {
-    this.simpleNamePattern = simpleNamePattern;
+  private KeepUnqualfiedClassNamePattern(KeepStringPattern unqualifiedNamePattern) {
+    this.unqualifiedNamePattern = unqualifiedNamePattern;
   }
 
   public boolean isAny() {
-    return simpleNamePattern.isAny();
+    return unqualifiedNamePattern.isAny();
   }
 
   public boolean isExact() {
-    return simpleNamePattern.isExact();
+    return unqualifiedNamePattern.isExact();
   }
 
   public String asExactString() {
-    return simpleNamePattern.asExactString();
+    return unqualifiedNamePattern.asExactString();
   }
 
   public KeepStringPattern asStringPattern() {
-    return simpleNamePattern;
+    return unqualifiedNamePattern;
   }
 
   @Override
@@ -55,17 +55,17 @@
       return false;
     }
     KeepUnqualfiedClassNamePattern that = (KeepUnqualfiedClassNamePattern) o;
-    return simpleNamePattern.equals(that.simpleNamePattern);
+    return unqualifiedNamePattern.equals(that.unqualifiedNamePattern);
   }
 
   @Override
   public int hashCode() {
-    return simpleNamePattern.hashCode();
+    return unqualifiedNamePattern.hashCode();
   }
 
   @Override
   public String toString() {
-    return simpleNamePattern.toString();
+    return unqualifiedNamePattern.toString();
   }
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index a16189b..a6e5d5f 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -396,6 +396,8 @@
     if (globalsOutputPath != null) {
       builder.setGlobalSyntheticsOutput(globalsOutputPath);
     }
-    return builder.setOutput(outputPath, outputMode);
+    builder.setOutput(outputPath, outputMode);
+    builder.setEnableExperimentalMissingLibraryApiModeling(true);
+    return builder;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index ccfe752..9b9989a 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -53,6 +53,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.android.tools.r8.utils.SelfRetraceTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -266,7 +267,8 @@
         DexEncodedField.EMPTY_ARRAY,
         MethodCollectionFactory.empty(),
         factory.getSkipNameValidationForTesting(),
-        DexProgramClass::invalidChecksumRequest);
+        DexProgramClass::invalidChecksumRequest,
+        ReachabilitySensitiveValue.DISABLED);
   }
 
   private static void createAllApiStubs(
@@ -287,7 +289,7 @@
           if (notModeledTypes.contains(libraryClass.getClassReference().getTypeName())) {
             return;
           }
-          if (ApiReferenceStubber.isJavaType(libraryClass.getType(), factory)) {
+          if (ApiReferenceStubber.isNeverStubbedType(libraryClass.getType(), factory)) {
             return;
           }
           KnownApiLevel knownApiLevel =
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7cabfb6..851df61 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -465,7 +465,7 @@
       new CfOpenClosedInterfacesAnalysis(appViewWithLiveness).run(executorService);
 
       // TODO(b/225838009): Move higher up.
-      LirConverter.enterLirSupportedPhase(appView, executorService);
+      LirConverter.enterLirSupportedPhase(appViewWithLiveness, executorService);
 
       assert verifyNoJarApplicationReaders(appView.appInfo().classes());
       assert appView.checkForTesting(() -> allReferencesAssignedApiLevel(appViewWithLiveness));
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index d3fafc4..f663fd4 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -159,7 +159,7 @@
   }
 
   private void findReferencedLibraryClasses(DexType type, DexProgramClass context) {
-    if (!type.isClassType() || isJavaType(type, appView.dexItemFactory())) {
+    if (!type.isClassType() || isNeverStubbedType(type, appView.dexItemFactory())) {
       return;
     }
     DexClass clazz = appView.definitionFor(type);
@@ -182,10 +182,13 @@
     }
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  public static boolean isJavaType(DexType type, DexItemFactory factory) {
+  public static boolean isNeverStubbedType(DexType type, DexItemFactory factory) {
+    return isJavaType(type, factory);
+  }
+
+  private static boolean isJavaType(DexType type, DexItemFactory factory) {
     DexString typeDescriptor = type.getDescriptor();
-    return type == factory.objectType
+    return type.isIdenticalTo(factory.objectType)
         || typeDescriptor.startsWith(factory.comSunDescriptorPrefix)
         || typeDescriptor.startsWith(factory.javaDescriptorPrefix)
         || typeDescriptor.startsWith(factory.javaxDescriptorPrefix)
@@ -201,7 +204,7 @@
       ApiReferenceStubberEventConsumer eventConsumer) {
     DexItemFactory factory = appView.dexItemFactory();
     // Do not stub the anything starting with java (including the object type).
-    if (isJavaType(libraryClass.getType(), factory)) {
+    if (isNeverStubbedType(libraryClass.getType(), factory)) {
       return;
     }
     // Check if desugared library will bridge the type.
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 5ddb8c1..22cf38f 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -73,6 +73,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -900,7 +901,10 @@
               virtualMethods,
               dexItemFactory.getSkipNameValidationForTesting(),
               checksumSupplier,
-              null);
+              null,
+              // Interpreting reachability sensitivity from DEX inputs is not supported.
+              // The compiler does not support building IR from DEX with debug information.
+              ReachabilitySensitiveValue.DISABLED);
       classCollection.accept(clazz); // Update the application object.
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 0e3e660..9d6df7a 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticMarker;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -38,7 +39,8 @@
               virtualMethods,
               skipNameValidationForTesting,
               checksumSupplier,
-              syntheticMarker) ->
+              syntheticMarker,
+              reachabilitySensitive) ->
               new DexProgramClass(
                   type,
                   originKind,
@@ -60,6 +62,7 @@
                   MethodCollectionFactory.fromMethods(directMethods, virtualMethods),
                   skipNameValidationForTesting,
                   checksumSupplier,
+                  reachabilitySensitive,
                   syntheticMarker),
           DexClass::isProgramClass);
   public static ClassKind<DexClasspathClass> CLASSPATH =
@@ -85,7 +88,8 @@
               virtualMethods,
               skipNameValidationForTesting,
               checksumSupplier,
-              syntheticMarker) ->
+              syntheticMarker,
+              unusedReachabilitySensitive) ->
               new DexClasspathClass(
                   type,
                   kind,
@@ -130,7 +134,8 @@
               virtualMethods,
               skipNameValidationForTesting,
               checksumSupplier,
-              syntheticMarker) ->
+              syntheticMarker,
+              unusedReachabilitySensitive) ->
               new DexLibraryClass(
                   type,
                   kind,
@@ -176,7 +181,8 @@
         DexEncodedMethod[] virtualMethods,
         boolean skipNameValidationForTesting,
         ChecksumSupplier checksumSupplier,
-        SyntheticMarker syntheticMarker);
+        SyntheticMarker syntheticMarker,
+        ReachabilitySensitiveValue reachabilitySensitive);
   }
 
   private final Factory<C> factory;
@@ -209,7 +215,8 @@
       DexEncodedMethod[] virtualMethods,
       boolean skipNameValidationForTesting,
       ChecksumSupplier checksumSupplier,
-      SyntheticMarker syntheticMarker) {
+      SyntheticMarker syntheticMarker,
+      ReachabilitySensitiveValue reachabilitySensitive) {
     return factory.create(
         type,
         kind,
@@ -232,7 +239,8 @@
         virtualMethods,
         skipNameValidationForTesting,
         checksumSupplier,
-        syntheticMarker);
+        syntheticMarker,
+        reachabilitySensitive);
   }
 
   public boolean isOfKind(DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 5f72db6..bdea90a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -302,6 +302,7 @@
     return true;
   }
 
+  @Override
   public FieldTypeSignature getGenericSignature() {
     return genericSignature;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index f7cc258..0365247 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature;
 import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo;
 import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
 import java.util.function.Consumer;
@@ -40,6 +41,8 @@
 
   public abstract void clearKotlinInfo();
 
+  public abstract DexDefinitionSignature<?> getGenericSignature();
+
   public abstract void clearGenericSignature();
 
   public DexType getHolderType() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 6c86c27..bd27dfc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1372,6 +1372,7 @@
     }
   }
 
+  @Override
   public MethodTypeSignature getGenericSignature() {
     return genericSignature;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index c43043c..a7e37bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -809,8 +809,11 @@
   public final DexType annotationCovariantReturnTypes =
       createStaticallyKnownType(
           "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;");
+
+  public final String annotationReachabilitySensitiveDesc =
+      "Ldalvik/annotation/optimization/ReachabilitySensitive;";
   public final DexType annotationReachabilitySensitive =
-      createStaticallyKnownType("Ldalvik/annotation/optimization/ReachabilitySensitive;");
+      createStaticallyKnownType(annotationReachabilitySensitiveDesc);
 
   private static final String METAFACTORY_METHOD_NAME = "metafactory";
   private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 68bc137..5b0aec4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticMarker;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -54,7 +54,7 @@
   private CfVersion initialClassFileVersion = null;
   private boolean deprecated = false;
   private KotlinClassLevelInfo kotlinInfo = getNoKotlinInfo();
-  private OptionalBool reachabilitySensitive = OptionalBool.unknown();
+  private final ReachabilitySensitiveValue reachabilitySensitive;
 
   private final ChecksumSupplier checksumSupplier;
 
@@ -81,6 +81,7 @@
       MethodCollectionFactory methodCollectionFactory,
       boolean skipNameValidationForTesting,
       ChecksumSupplier checksumSupplier,
+      ReachabilitySensitiveValue reachabilitySensitive,
       SyntheticMarker syntheticMarker) {
     super(
         sourceFile,
@@ -106,6 +107,7 @@
     this.originKind = originKind;
     this.checksumSupplier = checksumSupplier;
     this.syntheticMarker = syntheticMarker;
+    this.reachabilitySensitive = reachabilitySensitive;
   }
 
   public DexProgramClass(
@@ -128,7 +130,8 @@
       DexEncodedField[] instanceFields,
       MethodCollectionFactory methodCollectionFactory,
       boolean skipNameValidationForTesting,
-      ChecksumSupplier checksumSupplier) {
+      ChecksumSupplier checksumSupplier,
+      ReachabilitySensitiveValue reachabilitySensitive) {
     this(
         type,
         originKind,
@@ -150,6 +153,7 @@
         methodCollectionFactory,
         skipNameValidationForTesting,
         checksumSupplier,
+        reachabilitySensitive,
         null);
   }
 
@@ -174,7 +178,8 @@
         DexEncodedField.EMPTY_ARRAY,
         MethodCollectionFactory.empty(),
         false,
-        DexProgramClass::invalidChecksumRequest);
+        DexProgramClass::invalidChecksumRequest,
+        ReachabilitySensitiveValue.DISABLED);
   }
 
   @Override
@@ -204,24 +209,12 @@
    * that is the case, dead reference elimination is disabled and locals are kept alive for their
    * entire scope.
    */
-  public boolean getOrComputeReachabilitySensitive(AppView<?> appView) {
-    if (reachabilitySensitive.isUnknown()) {
-      reachabilitySensitive = OptionalBool.of(internalComputeReachabilitySensitive(appView));
-    }
-    return reachabilitySensitive.isTrue();
+  public boolean isReachabilitySensitive() {
+    return getReachabilitySensitiveValue().isEnabled();
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  private boolean internalComputeReachabilitySensitive(AppView<?> appView) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    for (DexEncodedMember<?, ?> member : members()) {
-      for (DexAnnotation annotation : member.annotations().annotations) {
-        if (annotation.annotation.type == dexItemFactory.annotationReachabilitySensitive) {
-          return true;
-        }
-      }
-    }
-    return false;
+  public ReachabilitySensitiveValue getReachabilitySensitiveValue() {
+    return reachabilitySensitive;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 1f97831..52ae66a 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -238,7 +238,11 @@
     public void addProgramClassPotentiallyOverridingNonProgramClass(DexProgramClass clazz) {
       addProgramClass(clazz);
       pendingClasspathRemovalIfPresent.add(clazz.type);
-      if (libraryClasses.containsKey(clazz.type)) {
+      // When java.lang.Record is added remove the library class, as this program class will
+      // eventually be renamed to com.android.tools.r8.RecordTag so there should not be a
+      // multi-resolution result when looking up java.lang.Record.
+      if (clazz.type.isIdenticalTo(options.dexItemFactory().recordType)
+          && libraryClasses.containsKey(clazz.type)) {
         ensureMutableLibraryClassesMap();
         libraryClasses.remove(clazz.type);
       }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index a339bde..29fc029 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -122,7 +122,7 @@
     return EMPTY_TYPE_SIGNATURES;
   }
 
-  interface DexDefinitionSignature<T extends DexDefinition> {
+  public interface DexDefinitionSignature<T extends DexDefinition> {
 
     default boolean isClassSignature() {
       return false;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 089194a..57b427e 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -48,10 +48,10 @@
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Iterables;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -258,7 +258,7 @@
     private final List<DexEncodedMethod> directMethods = new ArrayList<>();
     private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
     private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
-    private boolean hasReachabilitySensitiveMethod = false;
+    private boolean hasReachabilitySensitiveMember = false;
     private SyntheticMarker syntheticMarker = null;
 
     public CreateDexClassVisitor(
@@ -492,7 +492,6 @@
         addAnnotation(DexAnnotation.createAnnotationDefaultAnnotation(
             type, defaultAnnotations, application.getFactory()));
       }
-      checkReachabilitySensitivity();
       checkRecord();
       T clazz =
           classKind.create(
@@ -517,7 +516,8 @@
               virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
               application.getFactory().getSkipNameValidationForTesting(),
               getChecksumSupplier(classKind),
-              syntheticMarker);
+              syntheticMarker,
+              ReachabilitySensitiveValue.fromBoolean(hasReachabilitySensitiveMember));
       application.checkClassForMethodHandlesLookup(clazz, classKind);
       InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
       // A member class should not be a local or anonymous class.
@@ -599,33 +599,6 @@
       }
     }
 
-    // If anything is marked reachability sensitive, all methods need to be parsed including
-    // locals information. This propagates the reachability sensitivity bit so that if any field
-    // or method is annotated, all methods get parsed with locals information.
-    private void checkReachabilitySensitivity() {
-      if (hasReachabilitySensitiveMethod || hasReachabilitySensitiveField()) {
-        for (DexEncodedMethod method : Iterables.concat(directMethods, virtualMethods)) {
-          Code code = method.getCode();
-          if (code != null && code.isCfCode()) {
-            code.asLazyCfCode().markReachabilitySensitive();
-          }
-        }
-      }
-    }
-
-    @SuppressWarnings("ReferenceEquality")
-    private boolean hasReachabilitySensitiveField() {
-      DexType reachabilitySensitive = application.getFactory().annotationReachabilitySensitive;
-      for (DexEncodedField field : Iterables.concat(instanceFields, staticFields)) {
-        for (DexAnnotation annotation : field.annotations().annotations) {
-          if (annotation.annotation.type == reachabilitySensitive) {
-            return true;
-          }
-        }
-      }
-      return false;
-    }
-
     private void addDefaultAnnotation(String name, DexValue value) {
       if (defaultAnnotations == null) {
         defaultAnnotations = new ArrayList<>();
@@ -718,6 +691,11 @@
             parsingContext,
             parent.application::addKeepDeclaration);
       }
+
+      // Check for reachability sensitive annotation.
+      parent.hasReachabilitySensitiveMember |=
+          parent.application.getFactory().annotationReachabilitySensitiveDesc.equals(desc);
+
       return createAnnotationVisitor(
           desc, visible, getAnnotations(), parent.application, DexAnnotation::new);
     }
@@ -881,6 +859,11 @@
             parsingContext,
             parent.application::addKeepDeclaration);
       }
+
+      // Check for reachability sensitive annotation.
+      parent.hasReachabilitySensitiveMember |=
+          parent.application.getFactory().annotationReachabilitySensitiveDesc.equals(desc);
+
       return createAnnotationVisitor(
           desc, visible, getAnnotations(), parent.application, DexAnnotation::new);
     }
@@ -1030,7 +1013,6 @@
               .build();
       Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
       if (parent.methodSignatures.add(signature)) {
-        parent.hasReachabilitySensitiveMethod |= isReachabilitySensitive();
         if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
           parent.directMethods.add(dexMethod);
         } else {
@@ -1049,18 +1031,6 @@
       }
     }
 
-    @SuppressWarnings("ReferenceEquality")
-    private boolean isReachabilitySensitive() {
-      DexType reachabilitySensitive =
-          parent.application.getFactory().annotationReachabilitySensitive;
-      for (DexAnnotation annotation : getAnnotations()) {
-        if (annotation.annotation.type == reachabilitySensitive) {
-          return true;
-        }
-      }
-      return false;
-    }
-
     private List<DexAnnotation> getAnnotations() {
       if (annotations == null) {
         annotations = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index c41f8df..34c1b21 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -73,6 +73,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -119,12 +120,6 @@
   private JarApplicationReader application;
   private CfCode code;
   private ReparseContext context;
-  private boolean reachabilitySensitive = false;
-
-  public void markReachabilitySensitive() {
-    assert code == null;
-    reachabilitySensitive = true;
-  }
 
   @Override
   public boolean isCfCode() {
@@ -158,11 +153,17 @@
   private void internalParseCode() {
     ReparseContext context = this.context;
     JarApplicationReader application = this.application;
-    assert application != null;
     assert context != null;
+    assert application != null;
+    DexProgramClass programOwner = context.owner.asProgramClass();
+    ReachabilitySensitiveValue reachabilitySensitive =
+        programOwner != null
+            ? programOwner.getReachabilitySensitiveValue()
+            : ReachabilitySensitiveValue.DISABLED;
+    DebugParsingOptions parsingOptions = getParsingOptions(application, reachabilitySensitive);
     // The ClassCodeVisitor is in charge of setting this.context to null.
     try {
-      parseCode(context, false);
+      parseCode(context, false, parsingOptions);
     } catch (JsrEncountered e) {
       for (Code code : context.codeList) {
         code.asLazyCfCode().code = null;
@@ -170,7 +171,7 @@
         code.asLazyCfCode().application = application;
       }
       try {
-        parseCode(context, true);
+        parseCode(context, true, parsingOptions);
       } catch (JsrEncountered e1) {
         throw new Unreachable(e1);
       }
@@ -204,9 +205,8 @@
     }
   }
 
-  public void parseCode(ReparseContext context, boolean useJsrInliner) {
-    DebugParsingOptions parsingOptions = getParsingOptions(application, reachabilitySensitive);
-
+  private void parseCode(
+      ReparseContext context, boolean useJsrInliner, DebugParsingOptions parsingOptions) {
     ClassCodeVisitor classVisitor =
         new ClassCodeVisitor(
             context.owner,
@@ -301,7 +301,8 @@
 
   @Override
   public String toString() {
-    return asCfCode().toString();
+    // Don't force parsing in toString as it causes unexpected behavior when debugging.
+    return code != null ? code.toString() : "<lazy-code>";
   }
 
   @Override
@@ -1177,7 +1178,7 @@
   }
 
   private static DebugParsingOptions getParsingOptions(
-      JarApplicationReader application, boolean reachabilitySensitive) {
+      JarApplicationReader application, ReachabilitySensitiveValue reachabilitySensitive) {
     // TODO(b/166841731): We should compute our own from the compressed format.
     int parsingOptions =
         application.options.canUseInputStackMaps()
@@ -1194,7 +1195,7 @@
         configuration.isKeepParameterNames()
             || keep.localVariableTable
             || keep.localVariableTypeTable
-            || reachabilitySensitive;
+            || reachabilitySensitive.isEnabled();
     boolean lineInfo =
         (keep.lineNumberTable || application.options.canUseNativeDexPcInsteadOfDebugInfo());
     boolean methodParaeters = keep.methodParameters;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 03758b2..54dc05d 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -180,8 +180,8 @@
     return getDefinition().getKotlinInfo();
   }
 
-  public boolean getOrComputeReachabilitySensitive(AppView<?> appView) {
-    return getHolder().getOrComputeReachabilitySensitive(appView);
+  public boolean isReachabilitySensitive() {
+    return getHolder().isReachabilitySensitive();
   }
 
   public void setCode(Code newCode, AppView<?> appView) {
@@ -202,7 +202,7 @@
     if (appView.testing().noLocalsTableOnInput) {
       return false;
     }
-    return appView.options().debug || getOrComputeReachabilitySensitive(appView);
+    return appView.options().debug || isReachabilitySensitive();
   }
 
   public ProgramMethod rewrittenWithLens(
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
index ec65897..9f40e9f 100644
--- a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
@@ -113,7 +113,6 @@
     return newProgramClasses;
   }
 
-  @SuppressWarnings("ReferenceEquality")
   // Should remain private as the correctness of the fixup requires the lazy 'newProgramClasses'.
   private DexProgramClass fixupClass(DexProgramClass clazz) {
     DexProgramClass newClass =
@@ -137,7 +136,8 @@
             DexEncodedField.EMPTY_ARRAY,
             newHolder -> clazz.getMethodCollection().fixup(newHolder, this::fixupMethod),
             dexItemFactory.getSkipNameValidationForTesting(),
-            clazz.getChecksumSupplier());
+            clazz.getChecksumSupplier(),
+            clazz.getReachabilitySensitiveValue());
     newClass.setInstanceFields(fixupFields(clazz.instanceFields()));
     newClass.setStaticFields(fixupFields(clazz.staticFields()));
     // Transfer properties that are not passed to the constructor.
@@ -151,7 +151,7 @@
       newClass.setKotlinInfo(clazz.getKotlinInfo());
     }
     // If the class type changed, record the move in the lens.
-    if (newClass.getType() != clazz.getType()) {
+    if (newClass.getType().isNotIdenticalTo(clazz.getType())) {
       return recordClassChange(clazz, newClass);
     }
     return newClass;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
index 63461b5..fe5ea8b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -9,7 +9,9 @@
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.KeepInfo;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Iterables;
@@ -33,9 +35,9 @@
 
   private void processClass(DexProgramClass clazz) {
     DexType type = clazz.getType();
-    boolean pinHolder = keepInfo.getClassInfo(clazz).isPinned(options);
+    boolean pinHolder = isPinned(keepInfo.getClassInfo(clazz), clazz.getClassSignature());
     for (DexEncodedMember<?, ?> member : clazz.members()) {
-      if (keepInfo.getMemberInfo(member, clazz).isPinned(options)) {
+      if (isPinned(keepInfo.getMemberInfo(member, clazz), member.getGenericSignature())) {
         pinHolder = true;
         Iterables.addAll(
             dontMergeTypes,
@@ -49,6 +51,13 @@
     }
   }
 
+  private boolean isPinned(KeepInfo<?, ?> keepInfo, DexDefinitionSignature<?> genericSignature) {
+    return keepInfo.isPinned(options)
+        || (genericSignature.hasSignature()
+            && !options.isForceProguardCompatibilityEnabled()
+            && !keepInfo.isSignatureRemovalAllowed(options));
+  }
+
   @Override
   public boolean canMerge(DexProgramClass program) {
     return !dontMergeTypes.contains(program.getType());
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 0fbadd1..dba1c38 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -197,15 +198,17 @@
       if (clazz == null) {
         return true;
       }
-      if (clazz.superType == null) {
-        return false;
-      }
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       DexEncodedMethod resolutionResult =
           appInfo
               .resolveMethodOnClassLegacy(clazz, dexItemFactory.objectMembers.finalize)
               .getSingleTarget();
-      return resolutionResult != null && resolutionResult.isProgramMethod(appView);
+      if (resolutionResult == null) {
+        return false;
+      }
+      DexType holderType = resolutionResult.getHolderType();
+      return holderType.isNotIdenticalTo(dexItemFactory.objectType)
+          && holderType.isNotIdenticalTo(dexItemFactory.enumType);
     }
 
     return mayHaveFinalizeMethodDirectlyOrIndirectly(appView, baseType.asClassType());
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
index 488e60a..91e6d2d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
@@ -12,6 +12,8 @@
 
 public interface FieldPut {
 
+  BasicBlock getBlock();
+
   DexField getField();
 
   Position getPosition();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 7f7653b..5077746 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -632,7 +632,7 @@
 
   public boolean isConsistentSSABeforeTypesAreCorrectAllowingRedundantBlocks(AppView<?> appView) {
     assert isConsistentGraph(appView, true);
-    assert consistentBlockInstructions(appView, true);
+    assert consistentBlockInstructions(true);
     assert consistentDefUseChains();
     assert validThrowingInstructions();
     assert noCriticalEdges();
@@ -689,7 +689,7 @@
     assert consistentBlockNumbering();
     assert consistentPredecessorSuccessors();
     assert consistentCatchHandlers();
-    assert consistentBlockInstructions(appView, ssa);
+    assert consistentBlockInstructions(ssa);
     assert consistentMetadata();
     assert verifyAllThrowingInstructionsHavePositions();
     return true;
@@ -889,13 +889,11 @@
     return true;
   }
 
-  private boolean consistentBlockInstructions(AppView<?> appView, boolean ssa) {
+  private boolean consistentBlockInstructions(boolean ssa) {
     boolean argumentsAllowed = true;
     for (BasicBlock block : blocks) {
       assert block.consistentBlockInstructions(
-          argumentsAllowed,
-          options.debug || context().getOrComputeReachabilitySensitive(appView),
-          ssa);
+          argumentsAllowed, options.debug || context().isReachabilitySensitive(), ssa);
       argumentsAllowed = false;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 15082ac..4d73577 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -87,7 +87,7 @@
   public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     InternalOptions options = appView.options();
     if (options.debug
-        || code.context().getOrComputeReachabilitySensitive(appView)
+        || code.context().isReachabilitySensitive()
         || !code.getConversionOptions().isGeneratingDex()) {
       return DeadInstructionResult.notDead();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 7bafe1b..a6f39aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -200,15 +200,6 @@
     return operands;
   }
 
-  public boolean hasOperandThatMatches(Predicate<Value> predicate) {
-    for (Value operand : operands) {
-      if (predicate.test(operand)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   public void removeOperand(int index) {
     removeOperand(index, null, alwaysFalse());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index b95529b..8d03811 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -502,7 +502,7 @@
   }
 
   public boolean isDebugMode() {
-    return appView.options().debug || getProgramMethod().getOrComputeReachabilitySensitive(appView);
+    return appView.options().debug || getProgramMethod().isReachabilitySensitive();
   }
 
   public Int2ReferenceSortedMap<BlockInfo> getCFG() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 733a207..7383048 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -522,7 +522,7 @@
       previous = printMethod(code, "IR after disable assertions (SSA)", previous);
     }
 
-    boolean isDebugMode = options.debug || context.getOrComputeReachabilitySensitive(appView);
+    boolean isDebugMode = options.debug || context.isReachabilitySensitive();
     assert !method.isProcessed() || !isDebugMode
         : "Method already processed: "
             + context.toSourceString()
@@ -598,13 +598,6 @@
     CheckNotNullConverter.runIfNecessary(appView, code);
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
-    if (identifierNameStringMarker != null) {
-      timing.begin("Decouple identifier-name strings");
-      identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(code);
-      timing.end();
-      previous = printMethod(code, "IR after identifier-name strings (SSA)", previous);
-    }
-
     timing.begin("Run proto shrinking tasks");
     appView.withGeneratedExtensionRegistryShrinker(shrinker -> shrinker.rewriteCode(method, code));
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index c7f07e7..4a9e696 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -27,8 +27,10 @@
 import com.android.tools.r8.lightir.IR2LirConverter;
 import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.lightir.LirStrategy;
+import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.naming.RecordInvokeDynamicInvokeCustomRewriter;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -40,14 +42,16 @@
 public class LirConverter {
 
   public static void enterLirSupportedPhase(
-      AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+      AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
       throws ExecutionException {
     assert appView.testing().canUseLir(appView);
     assert appView.testing().isPreLirPhase();
     appView.testing().enterLirSupportedPhase();
     CodeRewriterPassCollection codeRewriterPassCollection =
         new CodeRewriterPassCollection(
-            new ConstResourceNumberRewriter(appView), new StringSwitchConverter(appView));
+            new ConstResourceNumberRewriter(appView),
+            new StringSwitchConverter(appView),
+            new IdentifierNameStringMarker(appView));
     // Convert code objects to LIR.
     ThreadUtils.processItems(
         appView.appInfo().classes(),
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
index c56ddbf..44cf6e7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
@@ -76,7 +76,7 @@
   }
 
   protected boolean isDebugMode(ProgramMethod context) {
-    return options.debug || context.getOrComputeReachabilitySensitive(appView);
+    return options.debug || context.isReachabilitySensitive();
   }
 
   protected abstract String getRewriterId();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index 8de8311..604299b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.IdentityHashMap;
@@ -89,7 +90,8 @@
             DexEncodedField.EMPTY_ARRAY,
             MethodCollectionFactory.fromMethods(newDirectMethods, newVirtualMethods),
             false,
-            emulatedInterface.getChecksumSupplier());
+            emulatedInterface.getChecksumSupplier(),
+            ReachabilitySensitiveValue.DISABLED);
     newEmulatedInterface.addExtraInterfaces(
         getRewrittenInterfacesOfEmulatedInterface(emulatedInterface), appView.dexItemFactory());
     return newEmulatedInterface;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index c6d8ac4..b6804e7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -151,7 +151,7 @@
     }
 
     ProgramMethod context = code.context();
-    if (context.getOrComputeReachabilitySensitive(appView)) {
+    if (context.isReachabilitySensitive()) {
       return ClassInitializerDefaultsResult.empty();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index b6e87c2..f5e2f36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ClassResolutionResult;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -224,7 +224,8 @@
       if (block.hasCatchHandlers()) {
         if (block.canThrow()) {
           if (appView.enableWholeProgramOptimizations()) {
-            Collection<CatchHandler<BasicBlock>> deadCatchHandlers = getDeadCatchHandlers(block);
+            Collection<CatchHandler<BasicBlock>> deadCatchHandlers =
+                getDeadCatchHandlers(code, block);
             if (!deadCatchHandlers.isEmpty()) {
               for (CatchHandler<BasicBlock> catchHandler : deadCatchHandlers) {
                 catchHandler.target.unlinkCatchHandlerForGuard(catchHandler.guard);
@@ -249,10 +250,8 @@
     return mayHaveIntroducedUnreachableBlocks;
   }
 
-  /**
-   * Returns the catch handlers of the given block that are dead, if any.
-   */
-  private Collection<CatchHandler<BasicBlock>> getDeadCatchHandlers(BasicBlock block) {
+  /** Returns the catch handlers of the given block that are dead, if any. */
+  private Collection<CatchHandler<BasicBlock>> getDeadCatchHandlers(IRCode code, BasicBlock block) {
     AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
     ImmutableList.Builder<CatchHandler<BasicBlock>> builder = ImmutableList.builder();
     CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
@@ -275,11 +274,19 @@
         continue;
       }
 
-      // We can exploit that a catch handler must be dead if its guard is never instantiated
-      // directly or indirectly.
       if (appInfoWithLiveness != null) {
-        DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(guard));
-        if (clazz != null && !appInfoWithLiveness.isInstantiatedDirectlyOrIndirectly(clazz)) {
+        ClassResolutionResult result =
+            appView.definitionForWithResolutionResult(guard, code.context().getContextClass());
+        if (!result.hasClassResolutionResult() || result.isMultipleClassResolutionResult()) {
+          // With a multi resolution result one of the results is a library class, so the guard
+          // cannot be removed.
+          continue;
+        }
+        // We can exploit that a catch handler must be dead if its guard is never instantiated
+        // directly or indirectly.
+        DexClass clazz = result.toSingleClassWithProgramOverLibrary();
+        if (clazz.isProgramClass()
+            && !appInfoWithLiveness.isInstantiatedDirectlyOrIndirectly(clazz.asProgramClass())) {
           builder.add(new CatchHandler<>(guard, target));
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 337d90d..bc22ae8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -480,6 +481,11 @@
       return null;
     }
 
+    if (isInliningBlockedDueToArrayClone(context, singleTarget, appView)) {
+      whyAreYouNotInliningReporter.reportUnsafeDueToArrayCloneCall();
+      return null;
+    }
+
     // Make sure constructor inlining is legal.
     if (singleTarget.getDefinition().isInstanceInitializer()
         && !canInlineInstanceInitializer(
@@ -624,6 +630,25 @@
     return false;
   }
 
+  public static boolean isInliningBlockedDueToArrayClone(
+      ProgramMethod context, ProgramMethod singleTarget, AppView<?> appView) {
+    if (!appView.options().canHaveArtArrayCloneFromInterfaceMethodBug()) {
+      return false;
+    }
+    if (!context.getHolder().isInterface()) {
+      return false;
+    }
+    return singleTarget.registerCodeReferencesWithResult(
+        new DefaultUseRegistryWithResult<>(appView, context, false) {
+          @Override
+          public void registerInvokeVirtual(DexMethod method) {
+            if (appView.dexItemFactory().isArrayClone(method)) {
+              setResult(true);
+            }
+          }
+        });
+  }
+
   @Override
   @SuppressWarnings("ReferenceEquality")
   public boolean canInlineInstanceInitializer(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 3d78982..4b9c547 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -160,7 +160,7 @@
     }
     ProgramMethod method = code.context();
     DexEncodedMethod definition = method.getDefinition();
-    if (definition.isClassInitializer() || method.getOrComputeReachabilitySensitive(appView)) {
+    if (definition.isClassInitializer() || method.isReachabilitySensitive()) {
       return ConstraintWithTarget.NEVER;
     }
     KeepMethodInfo keepInfo = appView.getKeepInfo(method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 23ae714..ab67530 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -432,9 +432,7 @@
               // instruction has side-effects that can change the value of fields. If so, it must be
               // handled above. If not, it can be safely added to the assert.
               assert instruction.isArgument()
-                      || instruction.isArrayGet()
                       || instruction.isArrayLength()
-                      || instruction.isArrayPut()
                       || instruction.isAssume()
                       || instruction.isBinop()
                       || instruction.isCheckCast()
@@ -627,6 +625,10 @@
     }
 
     private void handleArrayGet(InstructionListIterator it, ArrayGet arrayGet) {
+      if (arrayGet.instructionInstanceCanThrow(appView, method)) {
+        // The read might not happen if the array get can throw.
+        activeState.clearMostRecentFieldWrites();
+      }
       if (arrayGet.array().hasLocalInfo()) {
         // The array may be modified through the debugger. Therefore subsequent reads of the same
         // array slot may not read this local.
@@ -656,6 +658,12 @@
       int index = arrayPut.indexOrDefault(-1);
       MemberType memberType = arrayPut.getMemberType();
 
+      // If the instruction can throw, we can't use any previous field stores for store-after-store
+      // elimination.
+      if (arrayPut.instructionInstanceCanThrow(appView, method)) {
+        activeState.clearMostRecentFieldWrites();
+      }
+
       // An array-put instruction can potentially write the given array slot on all arrays because
       // of
       // aliases.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 6359da4..403719e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -57,6 +57,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -1340,6 +1341,9 @@
       // Using -allowaccessmodification mitigates this.
       return false;
     }
+    if (DefaultInliningOracle.isInliningBlockedDueToArrayClone(method, singleTarget, appView)) {
+      return false;
+    }
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 1f116b4..cb7523f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -137,4 +137,7 @@
   public boolean unsetReasonHasBeenReportedFlag() {
     return true;
   }
+
+  @Override
+  public void reportUnsafeDueToArrayCloneCall() {}
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index b333817..ca2d508 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -127,4 +127,6 @@
       int numberOfMonitorEnterValuesAfterInlining, int threshold);
 
   public abstract boolean unsetReasonHasBeenReportedFlag();
+
+  public abstract void reportUnsafeDueToArrayCloneCall();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 23391b7..8a04363 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -297,4 +297,10 @@
     reasonHasBeenReported = false;
     return true;
   }
+
+  @Override
+  public void reportUnsafeDueToArrayCloneCall() {
+    report(
+        "would lead to unsupported resolution of array clone() from within an interface method.");
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java
index bf3bc86..514ed67 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/phis/EffectivelyTrivialPhiOptimization.java
@@ -182,7 +182,10 @@
       return new SingleValueOrValue(worklist.getSeenSet());
     }
     if (foundDifferentOperandValuesWithSameAbstractValue) {
-      if (representativeOperandAbstractValue.isSingleValue()) {
+      if (representativeOperandAbstractValue.isSingleValue()
+          && representativeOperandAbstractValue
+              .asSingleValue()
+              .isMaterializableInContext(appView, code.context())) {
         return new SingleValueOrValue(
             worklist.getSeenSet(), representativeOperandAbstractValue.asSingleValue());
       }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 2acd209..c099750 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -233,7 +233,7 @@
     // and we do not actually want locals information in the output.
     if (options().debug) {
       computeDebugInfo(code, blocks, liveIntervals, this, liveAtEntrySets);
-    } else if (code.context().getOrComputeReachabilitySensitive(appView)) {
+    } else if (code.context().isReachabilitySensitive()) {
       InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instruction = it.next();
@@ -1661,7 +1661,7 @@
     // Set all free positions for possible registers to max integer.
     RegisterPositions freePositions = new RegisterPositionsImpl(registerConstraint + 1);
 
-    if ((options().debug || code.context().getOrComputeReachabilitySensitive(appView))
+    if ((options().debug || code.context().isReachabilitySensitive())
         && !code.method().accessFlags.isStatic()) {
       // If we are generating debug information or if the method is reachability sensitive,
       // we pin the this value register. The debugger expects to always be able to find it in
@@ -2712,7 +2712,7 @@
             }
           }
         }
-        if (appView.options().debug || code.context().getOrComputeReachabilitySensitive(appView)) {
+        if (appView.options().debug || code.context().isReachabilitySensitive()) {
           // In debug mode, or if the method is reachability sensitive, extend the live range
           // to cover the full scope of a local variable (encoded as debug values).
           int number = instruction.getNumber();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 0480ba2..0314b43 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -129,11 +129,11 @@
     KmClass kmClass = metadata.getKmClass();
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : hostClass.fields()) {
-      fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field);
+      fieldMap.put(toJvmFieldSignature(field.getReference()).toString(), field);
     }
     Map<String, DexEncodedMethod> methodMap = new HashMap<>();
     for (DexEncodedMethod method : hostClass.methods()) {
-      methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method);
+      methodMap.put(toJvmMethodSignature(method.getReference()).toString(), method);
     }
     ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder();
     KotlinMetadataMembersTracker originalMembersWithKotlinInfo =
@@ -143,7 +143,7 @@
           KotlinConstructorInfo.create(kmConstructor, factory, reporter);
       JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor);
       if (signature != null) {
-        DexEncodedMethod method = methodMap.get(signature.asString());
+        DexEncodedMethod method = methodMap.get(signature.toString());
         if (method != null) {
           method.setKotlinMemberInfo(constructorInfo);
           originalMembersWithKotlinInfo.add(method.getReference());
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index b435950..21f5469 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -70,10 +70,10 @@
       }
       KotlinFunctionInfo kotlinFunctionInfo =
           KotlinFunctionInfo.create(kmFunction, factory, reporter);
-      DexEncodedMethod method = methodSignatureMap.get(signature.asString());
+      DexEncodedMethod method = methodSignatureMap.get(signature.toString());
       if (method == null) {
         notBackedFunctions.add(kotlinFunctionInfo);
-        if (!isValidMethodDescriptor(signature.getDesc())) {
+        if (!isValidMethodDescriptor(signature.getDescriptor())) {
           // TODO(b/155536535): Enable this assert.
           // appView
           //     .options()
@@ -98,7 +98,7 @@
       boolean hasBacking = false;
       if (propertyProcessor.fieldSignature() != null) {
         DexEncodedField field =
-            fieldSignatureMap.get(propertyProcessor.fieldSignature().asString());
+            fieldSignatureMap.get(propertyProcessor.fieldSignature().toString());
         if (field != null) {
           hasBacking = true;
           field.setKotlinMemberInfo(kotlinPropertyInfo);
@@ -107,7 +107,7 @@
       }
       if (propertyProcessor.getterSignature() != null) {
         DexEncodedMethod method =
-            methodSignatureMap.get(propertyProcessor.getterSignature().asString());
+            methodSignatureMap.get(propertyProcessor.getterSignature().toString());
         if (method != null) {
           hasBacking = true;
           keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
@@ -118,7 +118,7 @@
       }
       if (propertyProcessor.setterSignature() != null) {
         DexEncodedMethod method =
-            methodSignatureMap.get(propertyProcessor.setterSignature().asString());
+            methodSignatureMap.get(propertyProcessor.setterSignature().toString());
         if (method != null) {
           hasBacking = true;
           keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
@@ -130,7 +130,7 @@
       if (propertyProcessor.syntheticMethodForAnnotationsSignature() != null) {
         DexEncodedMethod method =
             methodSignatureMap.get(
-                propertyProcessor.syntheticMethodForAnnotationsSignature().asString());
+                propertyProcessor.syntheticMethodForAnnotationsSignature().toString());
         if (method != null) {
           hasBacking = true;
           method.setKotlinMemberInfo(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
index bcf19a1..2c5598d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -34,7 +34,7 @@
     }
     return new KotlinJvmFieldSignatureInfo(
         fieldSignature.getName(),
-        KotlinTypeReference.fromDescriptor(fieldSignature.getDesc(), factory));
+        KotlinTypeReference.fromDescriptor(fieldSignature.getDescriptor(), factory));
   }
 
   boolean rewrite(Consumer<JvmFieldSignature> consumer, DexEncodedField field, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
index 687156e..917f242 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -51,7 +51,7 @@
       return null;
     }
     String name = methodSignature.getName();
-    String descriptor = methodSignature.getDesc();
+    String descriptor = methodSignature.getDescriptor();
     if (!KotlinMetadataUtils.isValidMethodDescriptor(descriptor)) {
       // If the method descriptor is invalid, keep it as invalid.
       return new KotlinJvmMethodSignatureInfo(methodSignature.getName(), descriptor);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
index 09f6a41..77673c0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -41,7 +41,7 @@
     JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function);
     if (signature != null) {
       for (DexEncodedMethod method : clazz.methods()) {
-        if (toJvmMethodSignature(method.getReference()).asString().equals(signature.asString())) {
+        if (toJvmMethodSignature(method.getReference()).toString().equals(signature.toString())) {
           method.setKotlinMemberInfo(kotlinFunctionInfo);
           return new KotlinLambdaInfo(kotlinFunctionInfo, true);
         }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index 6e532a3..c2f31ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -24,14 +24,11 @@
 import java.util.List;
 import java.util.function.Consumer;
 import kotlin.Metadata;
-import kotlin.metadata.KmExtensionType;
 import kotlin.metadata.KmProperty;
-import kotlin.metadata.KmPropertyExtensionVisitor;
-import kotlin.metadata.KmPropertyVisitor;
+import kotlin.metadata.jvm.JvmExtensionsKt;
 import kotlin.metadata.jvm.JvmFieldSignature;
+import kotlin.metadata.jvm.JvmMetadataVersion;
 import kotlin.metadata.jvm.JvmMethodSignature;
-import kotlin.metadata.jvm.JvmPropertyExtensionVisitor;
-import kotlin.metadata.jvm.KotlinClassMetadata;
 
 public class KotlinMetadataUtils {
 
@@ -105,49 +102,25 @@
       JvmMethodSignature methodSignature, int intArguments) {
     return new JvmMethodSignature(
         methodSignature.getName() + "$default",
-        methodSignature.getDesc().replace(")", "I".repeat(intArguments) + "Ljava/lang/Object;)"));
+        methodSignature
+            .getDescriptor()
+            .replace(")", "I".repeat(intArguments) + "Ljava/lang/Object;)"));
   }
 
   static class KmPropertyProcessor {
-    private JvmFieldSignature fieldSignature = null;
+    private final JvmFieldSignature fieldSignature;
     // Custom getter via @get:JvmName("..."). Otherwise, null.
-    private JvmMethodSignature getterSignature = null;
+    private final JvmMethodSignature getterSignature;
     // Custom getter via @set:JvmName("..."). Otherwise, null.
-    private JvmMethodSignature setterSignature = null;
-    private JvmMethodSignature syntheticMethodForAnnotationsSignature = null;
+    private final JvmMethodSignature setterSignature;
+    private final JvmMethodSignature syntheticMethodForAnnotationsSignature;
 
     KmPropertyProcessor(KmProperty kmProperty) {
-      kmProperty.accept(
-          new KmPropertyVisitor() {
-            @Override
-            @SuppressWarnings("ReferenceEquality")
-            public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
-              if (type != JvmPropertyExtensionVisitor.TYPE) {
-                return null;
-              }
-              return new JvmPropertyExtensionVisitor() {
-                @Override
-                public void visit(
-                    int flags,
-                    JvmFieldSignature fieldDesc,
-                    JvmMethodSignature getterDesc,
-                    JvmMethodSignature setterDesc) {
-                  assert fieldSignature == null : fieldSignature.asString();
-                  fieldSignature = fieldDesc;
-                  assert getterSignature == null : getterSignature.asString();
-                  getterSignature = getterDesc;
-                  assert setterSignature == null : setterSignature.asString();
-                  setterSignature = setterDesc;
-                }
-
-                @Override
-                public void visitSyntheticMethodForAnnotations(JvmMethodSignature signature) {
-                  assert syntheticMethodForAnnotationsSignature == null : signature.asString();
-                  syntheticMethodForAnnotationsSignature = signature;
-                }
-              };
-            }
-          });
+      fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
+      getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty);
+      setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty);
+      syntheticMethodForAnnotationsSignature =
+          JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty);
     }
 
     JvmFieldSignature fieldSignature() {
@@ -248,7 +221,12 @@
   }
 
   static int[] getCompatibleKotlinInfo() {
-    return KotlinClassMetadata.COMPATIBLE_METADATA_VERSION;
+    // The kotlin metadata changelog recommends:
+    // "Main migration path here is to replace KotlinClassMetadata.COMPATIBLE_METADATA_VERSION
+    // with new value with the same meaning: JvmMetadataVersion.LATEST_STABLE_SUPPORTED."
+    // The inspection error "Usage of Kotlin internal declaration from different module" does not
+    // prevent the code to work correctly.
+    return JvmMetadataVersion.LATEST_STABLE_SUPPORTED.toIntArray();
   }
 
   static <TKm> TKm consume(TKm tKm, Consumer<TKm> consumer) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index 15883d2..24c5af9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -15,6 +15,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -87,9 +88,7 @@
         indent,
         "Metadata.Class",
         sb,
-        newIndent -> {
-          KotlinMetadataWriter.appendKmClass(newIndent, sb, kMetadata.getKmClass());
-        });
+        newIndent -> KotlinMetadataWriter.appendKmClass(newIndent, sb, kMetadata.getKmClass()));
     return sb.toString();
   }
 
@@ -100,9 +99,7 @@
         indent,
         "Metadata.FileFacade",
         sb,
-        newIndent -> {
-          KotlinMetadataWriter.appendKmPackage(newIndent, sb, kMetadata.getKmPackage());
-        });
+        newIndent -> KotlinMetadataWriter.appendKmPackage(newIndent, sb, kMetadata.getKmPackage()));
     return sb.toString();
   }
 
@@ -144,9 +141,8 @@
                   newIndent,
                   "function",
                   sb,
-                  nextIndent -> {
-                    KotlinMetadataWriter.appendKmFunction(nextIndent, sb, kmLambda.function);
-                  });
+                  nextIndent ->
+                      KotlinMetadataWriter.appendKmFunction(nextIndent, sb, kmLambda.function));
             } else {
               KotlinMetadataWriter.appendKeyValue(newIndent, "function", sb, "null");
             }
@@ -216,70 +212,62 @@
         indent,
         "functions",
         sb,
-        newIndent -> {
-          appendKmList(
-              newIndent,
-              "KmFunction",
-              sb,
-              container.getFunctions().stream()
-                  .sorted(
-                      Comparator.comparing(
-                          kmFunction -> JvmExtensionsKt.getSignature(kmFunction).asString()))
-                  .collect(Collectors.toList()),
-              (nextIndent, kmFunction) -> {
-                appendKmFunction(nextIndent, sb, kmFunction);
-              });
-        });
+        newIndent ->
+            appendKmList(
+                newIndent,
+                "KmFunction",
+                sb,
+                container.getFunctions().stream()
+                    .sorted(
+                        Comparator.comparing(
+                            kmFunction ->
+                                Objects.toString(JvmExtensionsKt.getSignature(kmFunction))))
+                    .collect(Collectors.toList()),
+                (nextIndent, kmFunction) -> appendKmFunction(nextIndent, sb, kmFunction)));
     appendKeyValue(
         indent,
         "properties",
         sb,
-        newIndent -> {
-          appendKmList(
-              newIndent,
-              "KmProperty",
-              sb,
-              container.getProperties().stream()
-                  .sorted(
-                      Comparator.comparing(
-                          kmProperty -> {
-                            JvmMethodSignature signature =
-                                JvmExtensionsKt.getGetterSignature(kmProperty);
-                            if (signature != null) {
-                              return signature.asString();
-                            }
-                            signature = JvmExtensionsKt.getSetterSignature(kmProperty);
-                            if (signature != null) {
-                              return signature.asString();
-                            }
-                            JvmFieldSignature fieldSignature =
-                                JvmExtensionsKt.getFieldSignature(kmProperty);
-                            if (fieldSignature != null) {
-                              return fieldSignature.asString();
-                            }
-                            return kmProperty.getName();
-                          }))
-                  .collect(Collectors.toList()),
-              (nextIndent, kmProperty) -> {
-                appendKmProperty(nextIndent, sb, kmProperty);
-              });
-        });
+        newIndent ->
+            appendKmList(
+                newIndent,
+                "KmProperty",
+                sb,
+                container.getProperties().stream()
+                    .sorted(
+                        Comparator.comparing(
+                            kmProperty -> {
+                              JvmMethodSignature signature =
+                                  JvmExtensionsKt.getGetterSignature(kmProperty);
+                              if (signature != null) {
+                                return signature.toString();
+                              }
+                              signature = JvmExtensionsKt.getSetterSignature(kmProperty);
+                              if (signature != null) {
+                                return signature.toString();
+                              }
+                              JvmFieldSignature fieldSignature =
+                                  JvmExtensionsKt.getFieldSignature(kmProperty);
+                              if (fieldSignature != null) {
+                                return fieldSignature.toString();
+                              }
+                              return kmProperty.getName();
+                            }))
+                    .collect(Collectors.toList()),
+                (nextIndent, kmProperty) -> appendKmProperty(nextIndent, sb, kmProperty)));
     appendKeyValue(
         indent,
         "typeAliases",
         sb,
-        newIndent -> {
-          appendKmList(
-              newIndent,
-              "KmTypeAlias",
-              sb,
-              container.getTypeAliases().stream()
-                  .sorted(Comparator.comparing(KmTypeAlias::getName))
-                  .collect(Collectors.toList()),
-              (nextIndent, kmTypeAlias) -> {
-                appendTypeAlias(nextIndent, sb, kmTypeAlias);
-              });
-        });
+        newIndent ->
+            appendKmList(
+                newIndent,
+                "KmTypeAlias",
+                sb,
+                container.getTypeAliases().stream()
+                    .sorted(Comparator.comparing(KmTypeAlias::getName))
+                    .collect(Collectors.toList()),
+                (nextIndent, kmTypeAlias) -> appendTypeAlias(nextIndent, sb, kmTypeAlias)));
   }
 
   public static void appendKmPackage(String indent, StringBuilder sb, KmPackage kmPackage) {
@@ -289,16 +277,13 @@
         indent,
         "localDelegatedProperties",
         sb,
-        nextIndent -> {
-          appendKmList(
-              nextIndent,
-              "KmProperty",
-              sb,
-              JvmExtensionsKt.getLocalDelegatedProperties(kmPackage),
-              (nextNextIndent, kmProperty) -> {
-                appendKmProperty(nextNextIndent, sb, kmProperty);
-              });
-        });
+        nextIndent ->
+            appendKmList(
+                nextIndent,
+                "KmProperty",
+                sb,
+                JvmExtensionsKt.getLocalDelegatedProperties(kmPackage),
+                (nextNextIndent, kmProperty) -> appendKmProperty(nextNextIndent, sb, kmProperty)));
   }
 
   public static void appendKmClass(String indent, StringBuilder sb, KmClass kmClass) {
@@ -309,23 +294,18 @@
         indent,
         "typeParameters",
         sb,
-        newIndent -> {
-          appendTypeParameters(newIndent, sb, kmClass.getTypeParameters());
-        });
+        newIndent -> appendTypeParameters(newIndent, sb, kmClass.getTypeParameters()));
     appendKeyValue(
         indent,
         "superTypes",
         sb,
-        newIndent -> {
-          appendKmList(
-              newIndent,
-              "KmType",
-              sb,
-              kmClass.getSupertypes(),
-              (nextIndent, kmType) -> {
-                appendKmType(nextIndent, sb, kmType);
-              });
-        });
+        newIndent ->
+            appendKmList(
+                newIndent,
+                "KmType",
+                sb,
+                kmClass.getSupertypes(),
+                (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType)));
     if (kmClass.getInlineClassUnderlyingPropertyName() != null) {
       appendKeyValue(
           indent,
@@ -338,9 +318,7 @@
           indent,
           "inlineClassUnderlyingType",
           sb,
-          nextIndent -> {
-            appendKmType(nextIndent, sb, kmClass.getInlineClassUnderlyingType());
-          });
+          nextIndent -> appendKmType(nextIndent, sb, kmClass.getInlineClassUnderlyingType()));
     }
     String companionObject = kmClass.getCompanionObject();
     appendKeyValue(
@@ -364,16 +342,13 @@
         indent,
         "localDelegatedProperties",
         sb,
-        nextIndent -> {
-          appendKmList(
-              nextIndent,
-              "KmProperty",
-              sb,
-              JvmExtensionsKt.getLocalDelegatedProperties(kmClass),
-              (nextNextIndent, kmProperty) -> {
-                appendKmProperty(nextNextIndent, sb, kmProperty);
-              });
-        });
+        nextIndent ->
+            appendKmList(
+                nextIndent,
+                "KmProperty",
+                sb,
+                JvmExtensionsKt.getLocalDelegatedProperties(kmClass),
+                (nextNextIndent, kmProperty) -> appendKmProperty(nextNextIndent, sb, kmProperty)));
     appendKmVersionRequirement(indent, sb, kmClass.getVersionRequirements());
     appendKeyValue(
         indent,
@@ -388,11 +363,9 @@
                     .sorted(
                         Comparator.comparing(
                             kmConstructor ->
-                                JvmExtensionsKt.getSignature(kmConstructor).asString()))
+                                Objects.toString(JvmExtensionsKt.getSignature(kmConstructor))))
                     .collect(Collectors.toList()),
-                (nextIndent, constructor) -> {
-                  appendKmConstructor(nextIndent, sb, constructor);
-                }));
+                (nextIndent, constructor) -> appendKmConstructor(nextIndent, sb, constructor)));
     appendKeyValue(
         indent,
         "contextReceiverTypes",
@@ -422,8 +395,7 @@
               nextIndent ->
                   appendValueParameters(nextIndent, sb, constructor.getValueParameters()));
           JvmMethodSignature signature = JvmExtensionsKt.getSignature(constructor);
-          appendKeyValue(
-              newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+          appendKeyValue(newIndent, "signature", sb, Objects.toString(signature));
           appendKmVersionRequirement(newIndent, sb, constructor.getVersionRequirements());
         });
   }
@@ -465,9 +437,7 @@
                 newIndent,
                 "contract",
                 sb,
-                nextIndent -> {
-                  appendKmContract(nextIndent, sb, contract);
-                });
+                nextIndent -> appendKmContract(nextIndent, sb, contract));
           }
           appendKeyValue(
               newIndent,
@@ -481,8 +451,7 @@
                       function.getContextReceiverTypes(),
                       (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType)));
           JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
-          appendKeyValue(
-              newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+          appendKeyValue(newIndent, "signature", sb, Objects.toString(signature));
           appendKeyValue(
               newIndent,
               "lambdaClassOriginName",
@@ -535,39 +504,25 @@
                       (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType)));
           appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + "");
           JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
-          appendKeyValue(
-              newIndent,
-              "fieldSignature",
-              sb,
-              fieldSignature != null ? fieldSignature.asString() : "null");
+          appendKeyValue(newIndent, "fieldSignature", sb, Objects.toString(fieldSignature));
           JvmMethodSignature getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty);
-          appendKeyValue(
-              newIndent,
-              "getterSignature",
-              sb,
-              getterSignature != null ? getterSignature.asString() : "null");
+          appendKeyValue(newIndent, "getterSignature", sb, Objects.toString(getterSignature));
           JvmMethodSignature setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty);
-          appendKeyValue(
-              newIndent,
-              "setterSignature",
-              sb,
-              setterSignature != null ? setterSignature.asString() : "null");
+          appendKeyValue(newIndent, "setterSignature", sb, Objects.toString(setterSignature));
           JvmMethodSignature syntheticMethodForAnnotations =
               JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty);
           appendKeyValue(
               newIndent,
               "syntheticMethodForAnnotations",
               sb,
-              syntheticMethodForAnnotations != null
-                  ? syntheticMethodForAnnotations.asString()
-                  : "null");
+              Objects.toString(syntheticMethodForAnnotations));
           JvmMethodSignature syntheticMethodForDelegate =
               JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty);
           appendKeyValue(
               newIndent,
               "syntheticMethodForDelegate",
               sb,
-              syntheticMethodForDelegate != null ? syntheticMethodForDelegate.asString() : "null");
+              Objects.toString(syntheticMethodForDelegate));
         });
   }
 
@@ -587,16 +542,14 @@
               newIndent,
               "arguments",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmTypeProjection",
-                    sb,
-                    kmType.getArguments(),
-                    (nextNextIndent, kmTypeProjection) -> {
-                      appendKmTypeProjection(nextNextIndent, sb, kmTypeProjection);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmTypeProjection",
+                      sb,
+                      kmType.getArguments(),
+                      (nextNextIndent, kmTypeProjection) ->
+                          appendKmTypeProjection(nextNextIndent, sb, kmTypeProjection)));
           appendKeyValue(
               newIndent,
               "abbreviatedType",
@@ -613,42 +566,39 @@
                 newIndent,
                 "flexibleTypeUpperBound",
                 sb,
-                nextIndent -> {
-                  appendKmSection(
-                      newIndent,
-                      "FlexibleTypeUpperBound",
-                      sb,
-                      nextNextIndent -> {
-                        appendKeyValue(
-                            nextNextIndent,
-                            "typeFlexibilityId",
-                            sb,
-                            flexibleTypeUpperBound.getTypeFlexibilityId());
-                        appendKeyValue(
-                            nextNextIndent,
-                            "type",
-                            sb,
-                            nextNextNextIndent ->
-                                appendKmType(
-                                    nextNextNextIndent, sb, flexibleTypeUpperBound.getType()));
-                      });
-                });
+                nextIndent ->
+                    appendKmSection(
+                        newIndent,
+                        "FlexibleTypeUpperBound",
+                        sb,
+                        nextNextIndent -> {
+                          appendKeyValue(
+                              nextNextIndent,
+                              "typeFlexibilityId",
+                              sb,
+                              flexibleTypeUpperBound.getTypeFlexibilityId());
+                          appendKeyValue(
+                              nextNextIndent,
+                              "type",
+                              sb,
+                              nextNextNextIndent ->
+                                  appendKmType(
+                                      nextNextNextIndent, sb, flexibleTypeUpperBound.getType()));
+                        }));
           }
           appendKeyValue(newIndent, "raw", sb, JvmExtensionsKt.isRaw(kmType) + "");
           appendKeyValue(
               newIndent,
               "annotations",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmAnnotion",
-                    sb,
-                    JvmExtensionsKt.getAnnotations(kmType),
-                    (nextNextIndent, kmAnnotation) -> {
-                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmAnnotion",
+                      sb,
+                      JvmExtensionsKt.getAnnotations(kmType),
+                      (nextNextIndent, kmAnnotation) ->
+                          appendKmAnnotation(nextNextIndent, sb, kmAnnotation)));
         });
   }
 
@@ -663,9 +613,7 @@
               newIndent,
               "type",
               sb,
-              nextIndent -> {
-                appendKmType(nextIndent, sb, projection.getType());
-              });
+              nextIndent -> appendKmType(nextIndent, sb, projection.getType()));
           if (projection.getVariance() != null) {
             appendKeyValue(newIndent, "variance", sb, projection.getVariance().name());
           }
@@ -679,9 +627,7 @@
         "KmValueParameter",
         sb,
         valueParameters,
-        (newIndent, parameter) -> {
-          appendValueParameter(newIndent, sb, parameter);
-        });
+        (newIndent, parameter) -> appendValueParameter(newIndent, sb, parameter));
   }
 
   private static void appendValueParameter(
@@ -701,16 +647,12 @@
               newIndent,
               "type",
               sb,
-              nextIndent -> {
-                appendKmType(nextIndent, sb, valueParameter.getType());
-              });
+              nextIndent -> appendKmType(nextIndent, sb, valueParameter.getType()));
           appendKeyValue(
               newIndent,
               "varargElementType",
               sb,
-              nextIndent -> {
-                appendKmType(nextIndent, sb, valueParameter.getVarargElementType());
-              });
+              nextIndent -> appendKmType(nextIndent, sb, valueParameter.getVarargElementType()));
         });
   }
 
@@ -721,9 +663,7 @@
         "KmTypeParameter",
         sb,
         typeParameters,
-        (newIndent, parameter) -> {
-          appendTypeParameter(newIndent, sb, parameter);
-        });
+        (newIndent, parameter) -> appendTypeParameter(newIndent, sb, parameter));
   }
 
   private static void appendTypeParameter(
@@ -741,30 +681,25 @@
               newIndent,
               "upperBounds",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmType",
-                    sb,
-                    typeParameter.getUpperBounds(),
-                    (nextNextIndent, kmType) -> {
-                      appendKmType(nextNextIndent, sb, kmType);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmType",
+                      sb,
+                      typeParameter.getUpperBounds(),
+                      (nextNextIndent, kmType) -> appendKmType(nextNextIndent, sb, kmType)));
           appendKeyValue(
               newIndent,
               "extensions",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmAnnotion",
-                    sb,
-                    JvmExtensionsKt.getAnnotations(typeParameter),
-                    (nextNextIndent, kmAnnotation) -> {
-                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmAnnotion",
+                      sb,
+                      JvmExtensionsKt.getAnnotations(typeParameter),
+                      (nextNextIndent, kmAnnotation) ->
+                          appendKmAnnotation(nextNextIndent, sb, kmAnnotation)));
         });
   }
 
@@ -778,39 +713,31 @@
               newIndent,
               "annotations",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmAnnotation",
-                    sb,
-                    kmTypeAlias.getAnnotations(),
-                    (nextNextIndent, kmAnnotation) -> {
-                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmAnnotation",
+                      sb,
+                      kmTypeAlias.getAnnotations(),
+                      (nextNextIndent, kmAnnotation) ->
+                          appendKmAnnotation(nextNextIndent, sb, kmAnnotation)));
           appendKeyValue(
               newIndent,
               "expandedType",
               sb,
-              nextIndent -> {
-                appendKmType(nextIndent, sb, kmTypeAlias.expandedType);
-              });
+              nextIndent -> appendKmType(nextIndent, sb, kmTypeAlias.expandedType));
           appendKeyValue(newIndent, "flags", sb, kmTypeAlias.getFlags() + "");
           appendKeyValue(newIndent, "name", sb, kmTypeAlias.getName());
           appendKeyValue(
               newIndent,
               "typeParameters",
               sb,
-              nextIndent -> {
-                appendTypeParameters(nextIndent, sb, kmTypeAlias.getTypeParameters());
-              });
+              nextIndent -> appendTypeParameters(nextIndent, sb, kmTypeAlias.getTypeParameters()));
           appendKeyValue(
               newIndent,
               "underlyingType",
               sb,
-              nextIndent -> {
-                appendKmType(nextIndent, sb, kmTypeAlias.underlyingType);
-              });
+              nextIndent -> appendKmType(nextIndent, sb, kmTypeAlias.underlyingType));
           appendKmVersionRequirement(newIndent, sb, kmTypeAlias.getVersionRequirements());
         });
   }
@@ -834,21 +761,19 @@
                     "{ key: String, value: KmAnnotationArgument<?> }",
                     sb,
                     arguments.keySet(),
-                    (nextNextIndent, key) -> {
-                      appendKmSection(
-                          nextNextIndent,
-                          "",
-                          sb,
-                          nextNextNextIndent -> {
-                            appendKeyValue(
-                                nextNextNextIndent,
-                                key,
-                                sb,
-                                nextNextNextNextIndent -> {
-                                  appendKmArgument(nextNextNextIndent, sb, arguments.get(key));
-                                });
-                          });
-                    });
+                    (nextNextIndent, key) ->
+                        appendKmSection(
+                            nextNextIndent,
+                            "",
+                            sb,
+                            nextNextNextIndent ->
+                                appendKeyValue(
+                                    nextNextNextIndent,
+                                    key,
+                                    sb,
+                                    nextNextNextNextIndent ->
+                                        appendKmArgument(
+                                            nextNextNextIndent, sb, arguments.get(key)))));
               });
         });
   }
@@ -862,9 +787,7 @@
           "ArrayValue",
           sb,
           value,
-          (newIndent, annoArg) -> {
-            appendKmArgument(newIndent, sb, annoArg);
-          });
+          (newIndent, annoArg) -> appendKmArgument(newIndent, sb, annoArg));
     } else {
       sb.append(annotationArgument.toString());
     }
@@ -876,38 +799,37 @@
         indent,
         "versionRequirements",
         sb,
-        newIndent -> {
-          appendKmList(
-              newIndent,
-              "KmVersionRequirement",
-              sb,
-              kmVersionRequirements,
-              (nextIndent, kmVersionRequirement) -> {
-                appendKmSection(
-                    nextIndent,
-                    "KmVersionRequirement",
-                    sb,
-                    nextNextIndent -> {
-                      appendKeyValue(nextNextIndent, "kind", sb, kmVersionRequirement.kind.name());
-                      appendKeyValue(
-                          nextNextIndent, "level", sb, kmVersionRequirement.level.name());
-                      appendKeyValue(
-                          nextNextIndent,
-                          "errorCode",
-                          sb,
-                          kmVersionRequirement.getErrorCode() == null
-                              ? "null"
-                              : kmVersionRequirement.getErrorCode().toString());
-                      appendKeyValue(
-                          nextNextIndent, "message", sb, kmVersionRequirement.getMessage());
-                      appendKeyValue(
-                          nextNextIndent,
-                          "version",
-                          sb,
-                          kmVersionRequirement.getVersion().toString());
-                    });
-              });
-        });
+        newIndent ->
+            appendKmList(
+                newIndent,
+                "KmVersionRequirement",
+                sb,
+                kmVersionRequirements,
+                (nextIndent, kmVersionRequirement) ->
+                    appendKmSection(
+                        nextIndent,
+                        "KmVersionRequirement",
+                        sb,
+                        nextNextIndent -> {
+                          appendKeyValue(
+                              nextNextIndent, "kind", sb, kmVersionRequirement.kind.name());
+                          appendKeyValue(
+                              nextNextIndent, "level", sb, kmVersionRequirement.level.name());
+                          appendKeyValue(
+                              nextNextIndent,
+                              "errorCode",
+                              sb,
+                              kmVersionRequirement.getErrorCode() == null
+                                  ? "null"
+                                  : kmVersionRequirement.getErrorCode().toString());
+                          appendKeyValue(
+                              nextNextIndent, "message", sb, kmVersionRequirement.getMessage());
+                          appendKeyValue(
+                              nextNextIndent,
+                              "version",
+                              sb,
+                              kmVersionRequirement.getVersion().toString());
+                        })));
   }
 
   private static void appendKmContract(String indent, StringBuilder sb, KmContract contract) {
@@ -915,19 +837,18 @@
         indent,
         "KmContract",
         sb,
-        newIndent -> {
-          appendKeyValue(
-              newIndent,
-              "effects",
-              sb,
-              nextIndent ->
-                  appendKmList(
-                      nextIndent,
-                      "KmEffect",
-                      sb,
-                      contract.getEffects(),
-                      (nextNextIndent, effect) -> appendKmEffect(nextNextIndent, sb, effect)));
-        });
+        newIndent ->
+            appendKeyValue(
+                newIndent,
+                "effects",
+                sb,
+                nextIndent ->
+                    appendKmList(
+                        nextIndent,
+                        "KmEffect",
+                        sb,
+                        contract.getEffects(),
+                        (nextNextIndent, effect) -> appendKmEffect(nextNextIndent, sb, effect))));
   }
 
   private static void appendKmEffect(String indent, StringBuilder sb, KmEffect effect) {
@@ -946,16 +867,14 @@
               newIndent,
               "constructorArguments",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmEffectExpression",
-                    sb,
-                    effect.getConstructorArguments(),
-                    (nextNextIndent, expression) -> {
-                      appendKmEffectExpression(nextNextIndent, sb, expression);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmEffectExpression",
+                      sb,
+                      effect.getConstructorArguments(),
+                      (nextNextIndent, expression) ->
+                          appendKmEffectExpression(nextNextIndent, sb, expression)));
           KmEffectExpression conclusion = effect.getConclusion();
           if (conclusion == null) {
             appendKeyValue(newIndent, "conclusion", sb, "null");
@@ -995,37 +914,31 @@
               newIndent,
               "isInstanceType",
               sb,
-              nextIndent -> {
-                appendKmType(nextIndent, sb, expression.isInstanceType());
-              });
+              nextIndent -> appendKmType(nextIndent, sb, expression.isInstanceType()));
           appendKeyValue(
               newIndent,
               "andArguments",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmEffectExpression",
-                    sb,
-                    expression.getAndArguments(),
-                    (nextNextIndent, expr) -> {
-                      appendKmEffectExpression(nextNextIndent, sb, expr);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmEffectExpression",
+                      sb,
+                      expression.getAndArguments(),
+                      (nextNextIndent, expr) ->
+                          appendKmEffectExpression(nextNextIndent, sb, expr)));
           appendKeyValue(
               newIndent,
               "orArguments",
               sb,
-              nextIndent -> {
-                appendKmList(
-                    nextIndent,
-                    "KmEffectExpression",
-                    sb,
-                    expression.getOrArguments(),
-                    (nextNextIndent, expr) -> {
-                      appendKmEffectExpression(nextNextIndent, sb, expr);
-                    });
-              });
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmEffectExpression",
+                      sb,
+                      expression.getOrArguments(),
+                      (nextNextIndent, expr) ->
+                          appendKmEffectExpression(nextNextIndent, sb, expr)));
         });
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index c4cb846..2771257 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -45,11 +45,11 @@
       Consumer<DexEncodedMethod> keepByteCode) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : clazz.fields()) {
-      fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field);
+      fieldMap.put(toJvmFieldSignature(field.getReference()).toString(), field);
     }
     Map<String, DexEncodedMethod> methodMap = new HashMap<>();
     for (DexEncodedMethod method : clazz.methods()) {
-      methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method);
+      methodMap.put(toJvmMethodSignature(method.getReference()).toString(), method);
     }
     KotlinMetadataMembersTracker originalMembersWithKotlinInfo =
         new KotlinMetadataMembersTracker(appView);
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 14a837a..cdec5d9 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isClassNameComparison;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -23,7 +24,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
-import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.FieldPut;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
@@ -32,16 +33,19 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
-import java.util.Arrays;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Objects;
@@ -50,16 +54,33 @@
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
-public class IdentifierNameStringMarker {
+public class IdentifierNameStringMarker extends CodeRewriterPass<AppInfoWithLiveness> {
 
-  private final AppView<AppInfoWithLiveness> appView;
   private final Object2BooleanMap<DexMember<?, ?>> identifierNameStrings;
 
   public IdentifierNameStringMarker(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
+    super(appView);
     this.identifierNameStrings = appView.appInfo().identifierNameStrings;
   }
 
+  @Override
+  protected CodeRewriterResult rewriteCode(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    return decoupleIdentifierNameStringsInBlocks(code, null);
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return code.metadata().mayHaveConstString();
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "IdentifierNameStringMarker";
+  }
+
   public void decoupleIdentifierNameStringsInFields(
       ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(
@@ -83,21 +104,18 @@
       return;
     }
     DexString original = staticValue.getValue();
-    DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
+    DexReference itemBasedString = inferMemberOrTypeFromNameString(appView(), original);
     if (itemBasedString != null) {
       encodedField.setStaticValue(
           new DexItemBasedValueString(itemBasedString, ClassNameComputationInfo.none()));
     }
   }
 
-  public void decoupleIdentifierNameStringsInMethod(IRCode code) {
-    decoupleIdentifierNameStringsInBlocks(code, null);
-    assert code.isConsistentSSA(appView);
-  }
-
-  public void decoupleIdentifierNameStringsInBlocks(IRCode code, Set<BasicBlock> blocks) {
+  public CodeRewriterResult decoupleIdentifierNameStringsInBlocks(
+      IRCode code, Set<BasicBlock> blocks) {
+    CodeRewriterResult result = CodeRewriterResult.NO_CHANGE;
     if (!code.metadata().mayHaveConstString()) {
-      return;
+      return result;
     }
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
@@ -119,56 +137,69 @@
         // ...
         // v_n' <- DexItemBasedString("Lx/y/z;") // decoupled
         // this.fld <- v_n' // fieldPut
-        if (instruction.isStaticPut() || instruction.isInstancePut()) {
-          iterator =
-              decoupleIdentifierNameStringForFieldPutInstruction(
-                  code, blockIterator, iterator, instruction.asFieldInstruction());
+        if (instruction.isFieldPut()) {
+          FieldPut fieldPut = instruction.asFieldPut();
+          DexReference itemBasedString = getItemBasedStringForFieldPut(code, fieldPut);
+          if (itemBasedString != null) {
+            iterator =
+                decoupleIdentifierNameStringForFieldPutInstruction(
+                    code, blockIterator, iterator, fieldPut, itemBasedString);
+            result = CodeRewriterResult.HAS_CHANGED;
+          }
         } else if (instruction.isInvokeMethod()) {
+          InvokeMethod invoke = instruction.asInvokeMethod();
           iterator =
               decoupleIdentifierNameStringForInvokeInstruction(
-                  code, blockIterator, iterator, instruction.asInvokeMethod());
+                  code, blockIterator, iterator, invoke);
+          result = CodeRewriterResult.HAS_CHANGED;
         }
       }
     }
+    return result;
+  }
+
+  private DexReference getItemBasedStringForFieldPut(IRCode code, FieldPut fieldPut) {
+    DexField field = fieldPut.getField();
+    if (!identifierNameStrings.containsKey(field)) {
+      return null;
+    }
+    Value in = fieldPut.value();
+    if (in.isDexItemBasedConstString()) {
+      return null;
+    }
+    if (!in.isConstString()) {
+      warnUndeterminedIdentifierIfNecessary(
+          field, code.context(), fieldPut.asFieldInstruction(), null);
+      return null;
+    }
+    DexString original = in.getConstInstruction().asConstString().getValue();
+    DexReference itemBasedString = inferMemberOrTypeFromNameString(appView(), original);
+    if (itemBasedString == null) {
+      warnUndeterminedIdentifierIfNecessary(
+          field, code.context(), fieldPut.asFieldInstruction(), original);
+      return null;
+    }
+    return itemBasedString;
   }
 
   private InstructionListIterator decoupleIdentifierNameStringForFieldPutInstruction(
       IRCode code,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
-      FieldInstruction instruction) {
-    assert instruction.isInstancePut() || instruction.isStaticPut();
-    FieldInstruction fieldPut = instruction.asFieldInstruction();
-    DexField field = fieldPut.getField();
-    if (!identifierNameStrings.containsKey(field)) {
-      return iterator;
-    }
-    Value in = instruction.value();
-    if (in.isDexItemBasedConstString()) {
-      return iterator;
-    }
-    if (!in.isConstString()) {
-      warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, null);
-      return iterator;
-    }
-    DexString original = in.getConstInstruction().asConstString().getValue();
-    DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
-    if (itemBasedString == null) {
-      warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, original);
-      return iterator;
-    }
+      FieldPut fieldPut,
+      DexReference itemBasedString) {
     // Move the cursor back to $fieldPut
     assert iterator.peekPrevious() == fieldPut;
     iterator.previous();
     // Prepare $decoupled just before $fieldPut
-    Value newIn = code.createValue(in.getType(), in.getLocalInfo());
+    Value newIn = code.createValue(fieldPut.value().getType(), fieldPut.value().getLocalInfo());
     DexItemBasedConstString decoupled =
         new DexItemBasedConstString(newIn, itemBasedString, ClassNameComputationInfo.none());
     decoupled.setPosition(fieldPut.getPosition());
     // If the current block has catch handler, split into two blocks.
     // Because const-string we're about to add is also a throwing instr, we need to split
     // before adding it.
-    BasicBlock block = instruction.getBlock();
+    BasicBlock block = fieldPut.getBlock();
     BasicBlock blockWithFieldInstruction =
         block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
     if (blockWithFieldInstruction != block) {
@@ -186,12 +217,13 @@
       assert iterator.peekNext() == fieldPut;
       iterator.next();
     }
-    if (instruction.isStaticPut()) {
-      iterator.replaceCurrentInstruction(new StaticPut(newIn, field));
+    if (fieldPut.isStaticPut()) {
+      iterator.replaceCurrentInstruction(new StaticPut(newIn, fieldPut.getField()));
     } else {
-      assert instruction.isInstancePut();
-      InstancePut instancePut = instruction.asInstancePut();
-      iterator.replaceCurrentInstruction(new InstancePut(field, instancePut.object(), newIn));
+      assert fieldPut.isInstancePut();
+      InstancePut instancePut = fieldPut.asInstancePut();
+      iterator.replaceCurrentInstruction(
+          new InstancePut(fieldPut.getField(), instancePut.object(), newIn));
     }
     return iterator;
   }
@@ -282,7 +314,7 @@
           continue;
         }
         DexString original = in.getConstInstruction().asConstString().getValue();
-        DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
+        DexReference itemBasedString = inferMemberOrTypeFromNameString(appView(), original);
         if (itemBasedString == null) {
           warnUndeterminedIdentifierIfNecessary(invokedMethod, code.context(), invoke, original);
           continue;
@@ -320,16 +352,17 @@
         }
       }
     }
-    if (!Arrays.stream(changes).allMatch(Objects::isNull)) {
-      List<Value> newIns =
-          Streams.mapWithIndex(
-                  ins.stream(),
-                  (in, index) -> changes[(int) index] != null ? changes[(int) index] : in)
-              .collect(Collectors.toList());
-      iterator.replaceCurrentInstruction(
-          Invoke.create(
-              invoke.getType(), invokedMethod, invokedMethod.proto, invoke.outValue(), newIns));
+    if (ArrayUtils.none(changes, Objects::nonNull)) {
+      return iterator;
     }
+    List<Value> newIns =
+        Streams.mapWithIndex(
+                ins.stream(),
+                (in, index) -> changes[(int) index] != null ? changes[(int) index] : in)
+            .collect(Collectors.toList());
+    iterator.replaceCurrentInstruction(
+        Invoke.create(
+            invoke.getType(), invokedMethod, invokedMethod.proto, invoke.outValue(), newIns));
     return iterator;
   }
 
@@ -372,8 +405,10 @@
     }
     Origin origin = method.getOrigin();
     String kind = member.isDexField() ? "field" : "method";
-    String originalMessage = original == null ? "what identifier string flows to "
-        : "what '" + original.toString() + "' refers to, which flows to ";
+    String originalMessage =
+        original == null
+            ? "what identifier string flows to "
+            : "what '" + original + "' refers to, which flows to ";
     String message =
         "Cannot determine " + originalMessage + member.toSourceString()
             + " that is specified in -identifiernamestring rules."
diff --git a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
index 39d4bd8..cbaf559 100644
--- a/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/naming/KotlinModuleSynthesizer.java
@@ -26,6 +26,7 @@
 import java.util.Optional;
 import kotlin.metadata.jvm.JvmMetadataVersion;
 import kotlin.metadata.jvm.KmModule;
+import kotlin.metadata.jvm.KmPackageParts;
 import kotlin.metadata.jvm.KotlinModuleMetadata;
 
 /**
@@ -171,15 +172,14 @@
                   String rewrittenName = pair.getSecond();
                   multiClassPartToOriginal
                       .getOrDefault(originalName, Collections.emptyList())
-                      .forEach(
-                          classPart -> {
-                            newMultiFiles.put(classPart, rewrittenName);
-                          });
+                      .forEach(classPart -> newMultiFiles.put(classPart, rewrittenName));
                 });
-        kmModule.visitPackageParts(
-            newPackage,
-            newFacades.getOrDefault(newPackage, Collections.emptyList()),
-            newMultiFiles);
+        kmModule
+            .getPackageParts()
+            .put(
+                newPackage,
+                new KmPackageParts(
+                    newFacades.getOrDefault(newPackage, Collections.emptyList()), newMultiFiles));
       }
       return Optional.of(
           DataEntryResource.fromBytes(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index e750309..80cf637 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -7,8 +7,6 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
@@ -19,8 +17,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
@@ -30,6 +26,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -38,7 +35,6 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -48,7 +44,6 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 public class DefaultFieldValueJoiner {
 
@@ -74,9 +69,6 @@
     // Find all the fields where we need to determine if each field read is guaranteed to be
     // dominated by a write.
     Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = getFieldsOfInterest();
-    if (fieldsOfInterest.isEmpty()) {
-      return Collections.emptyMap();
-    }
 
     // If constructor inlining is disabled, then we focus on whether each instance initializer
     // definitely assigns the given field before it is read. We do the same for final and static
@@ -102,12 +94,12 @@
     // constructor inlining, we find all new-instance instructions (including subtype allocations)
     // and check if the field is written on each allocation before it is possibly read.
     analyzeNewInstanceInstructions(
-        fieldsNotSubjectToInitializerAnalysis, fieldsWithLiveDefaultValue::add, executorService);
+        fieldsNotSubjectToInitializerAnalysis, fieldsWithLiveDefaultValue::add);
 
     return updateFlowGraphs(fieldsWithLiveDefaultValue, executorService);
   }
 
-  protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
+  private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
     Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       clazz.forEachProgramField(
@@ -248,95 +240,13 @@
 
   private void analyzeNewInstanceInstructions(
       Map<DexType, ProgramFieldSet> nonFinalInstanceFields,
-      Consumer<ProgramField> liveDefaultValueConsumer,
-      ExecutorService executorService)
-      throws ExecutionException {
-    // To simplify the analysis, we currently bail out for non-final classes.
-    // TODO(b/296030319): Handle non-final classes.
-    MapUtils.removeIf(
-        nonFinalInstanceFields,
-        (holderType, fields) -> {
-          assert !fields.isEmpty();
-          DexProgramClass holder = fields.iterator().next().getHolder();
-          if (holder.isFinal() || !appView.appInfo().isInstantiatedIndirectly(holder)) {
-            // When the class is not explicitly marked final, the class could in principle have
-            // injected subclasses if it is pinned. However, none of the fields are pinned, so we
-            // should be allowed to reason about the field assignments in the program.
-            assert fields.stream()
-                .allMatch(
-                    field -> appView.getKeepInfo(field).isValuePropagationAllowed(appView, field));
-            return false;
-          }
-          fields.forEach(liveDefaultValueConsumer);
-          return true;
-        });
-
-    // We analyze all allocations of the classes that declare one of the given fields.
-    ThreadUtils.processMethods(
-        appView,
-        method ->
-            analyzeNewInstanceInstructionsInMethod(
-                nonFinalInstanceFields, liveDefaultValueConsumer, method),
-        appView.options().getThreadingModule(),
-        executorService);
-  }
-
-  private void analyzeNewInstanceInstructionsInMethod(
-      Map<DexType, ProgramFieldSet> nonFinalInstanceFields,
-      Consumer<ProgramField> liveDefaultValueConsumer,
-      ProgramMethod method) {
-    if (!maybeHasNewInstanceThatMatches(method, nonFinalInstanceFields::containsKey)) {
-      return;
+      Consumer<ProgramField> liveDefaultValueConsumer) {
+    // Conservatively treat all fields as maybe read before written.
+    // TODO(b/296030319): Implement analysis by building IR for all methods that instantiate the
+    //  relevant classes and analyzing the puts to the newly created instances.
+    for (ProgramField field : IterableUtils.flatten(nonFinalInstanceFields.values())) {
+      liveDefaultValueConsumer.accept(field);
     }
-    IRCode code = method.buildIR(appView, MethodConversionOptions.nonConverting());
-    for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
-      ProgramFieldSet fieldsOfInterest = nonFinalInstanceFields.get(newInstance.getType());
-      if (fieldsOfInterest == null) {
-        continue;
-      }
-      FieldReadBeforeWriteDfsAnalysis analysis =
-          new FieldReadBeforeWriteDfsAnalysis(appView, code, fieldsOfInterest, newInstance) {
-
-            @Override
-            public AnalysisContinuation acceptFieldMaybeReadBeforeWrite(ProgramField field) {
-              // Remove this field from the `fieldsOfInterest`, so that we do not spend more time
-              // analyzing it.
-              if (fieldsOfInterest.remove(field)) {
-                liveDefaultValueConsumer.accept(field);
-              }
-              return AnalysisContinuation.abortIf(fieldsOfInterest.isEmpty());
-            }
-          };
-      analysis.run();
-      if (fieldsOfInterest.isEmpty()) {
-        nonFinalInstanceFields.remove(newInstance.getType());
-      }
-    }
-  }
-
-  private boolean maybeHasNewInstanceThatMatches(
-      ProgramMethod method, Predicate<DexType> predicate) {
-    Code code = method.getDefinition().getCode();
-    if (code == null || code.isSharedCodeObject()) {
-      return false;
-    }
-    if (code.isLirCode()) {
-      return code.asLirCode()
-          .hasConstantItemThatMatches(
-              constant -> constant instanceof DexType && predicate.test((DexType) constant));
-    }
-    assert appView.isCfByteCodePassThrough(method);
-    assert code.isCfCode();
-    return method.registerCodeReferencesWithResult(
-        new DefaultUseRegistryWithResult<>(appView, method, false) {
-
-          @Override
-          public void registerNewInstance(DexType type) {
-            if (predicate.test(type)) {
-              setResult(true);
-            }
-          }
-        });
   }
 
   private Map<FlowGraph, Deque<FlowGraphNode>> updateFlowGraphs(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java
deleted file mode 100644
index 1fdfb27..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysis.java
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.optimize.argumentpropagation.propagation;
-
-import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
-import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
-import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
-import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
-import static com.android.tools.r8.ir.code.Opcodes.GOTO;
-import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
-import static com.android.tools.r8.ir.code.Opcodes.RETURN;
-import static com.android.tools.r8.ir.code.Opcodes.THROW;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockInstructionIterator;
-import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstNumber;
-import com.android.tools.r8.ir.code.ConstString;
-import com.android.tools.r8.ir.code.Goto;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.Throw;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.collections.ProgramFieldSet;
-
-/**
- * Analysis that is given an allocation site (a {@link NewInstance} instruction) and a set of fields
- * that belong to that newly allocated instance.
- *
- * <p>The analysis computes the subset of the given fields which are maybe read before they are
- * written. The default value of these fields is potentially read, whereas the default value of the
- * complement field set are guaranteed to never be read.
- *
- * <p>The analysis works by exploring all possible paths starting from the given allocation site to
- * the normal and the exceptional exits of the method, keeping track of which fields are definitely
- * written before they are read and which fields have maybe been read.
- */
-public abstract class FieldReadBeforeWriteDfsAnalysis extends FieldReadBeforeWriteDfsAnalysisState {
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final IRCode code;
-  private final DexItemFactory dexItemFactory;
-  // The set of fields to consider. Note that this is a concurrent set and that the caller may
-  // concurrently remove fields from the set. This may happen if we concurrently find a
-  // read-before-write of one of the fields.
-  private final ProgramFieldSet fields;
-  private final WorkList<WorkItem> worklist = WorkList.newIdentityWorkList();
-
-  private final FieldReadBeforeWriteDfsAnalysis self = this;
-
-  public FieldReadBeforeWriteDfsAnalysis(
-      AppView<AppInfoWithLiveness> appView,
-      IRCode code,
-      ProgramFieldSet fields,
-      NewInstance newInstance) {
-    super(newInstance);
-    this.appView = appView;
-    this.code = code;
-    this.dexItemFactory = appView.dexItemFactory();
-    this.fields = fields;
-  }
-
-  // Returns ABORT if all fields of interest are now maybe-read-before-written.
-  // Otherwise returns CONTINUE.
-  public abstract AnalysisContinuation acceptFieldMaybeReadBeforeWrite(ProgramField field);
-
-  public void run() {
-    worklist.addIfNotSeen(new InitialWorkItem());
-    worklist.run(WorkItem::process);
-  }
-
-  public enum AnalysisContinuation {
-    // Signals to abort the analysis completely (i.e., to break out of the DFS). This is used when
-    // we've reported all fields as being maybe read before written.
-    ABORT,
-    // Signals to continue the current DFS.
-    CONTINUE,
-    // Signals that all fields have been written before they are read on the current program path,
-    // meaning that the algorithm does not need to explore any further. The algorithm should instead
-    // backtrack and explore any other program paths.
-    BREAK;
-
-    static AnalysisContinuation abortIf(boolean condition) {
-      if (condition) {
-        return ABORT;
-      }
-      return CONTINUE;
-    }
-
-    boolean isAbort() {
-      return this == ABORT;
-    }
-
-    boolean isAbortOrContinue() {
-      return isAbort() || isContinue();
-    }
-
-    boolean isBreak() {
-      return this == BREAK;
-    }
-
-    boolean isContinue() {
-      return this == CONTINUE;
-    }
-
-    TraversalContinuation<?, ?> toTraversalContinuation() {
-      assert isAbortOrContinue();
-      return TraversalContinuation.breakIf(isAbort());
-    }
-  }
-
-  abstract class WorkItem {
-
-    abstract TraversalContinuation<?, ?> process();
-
-    void applyPhis(BasicBlock block) {
-      // TODO(b/339210038): When adding support for non-linear control flow, we need to implement
-      //  backtracking of this (i.e., we should remove the out value from the object aliases again).
-      for (Phi phi : block.getPhis()) {
-        if (phi.hasOperandThatMatches(self::isMaybeInstance)) {
-          addInstanceAlias(phi);
-        }
-      }
-    }
-
-    AnalysisContinuation applyInstructions(BasicBlockInstructionIterator instructionIterator) {
-      while (instructionIterator.hasNext()) {
-        Instruction instruction = instructionIterator.next();
-        assert !instruction.hasOutValue() || !isMaybeInstance(instruction.outValue());
-        AnalysisContinuation continuation;
-        // TODO(b/339210038): Extend this to many other instructions, such as ConstClass,
-        //  InstanceOf, *Binop, etc.
-        switch (instruction.opcode()) {
-          case ASSUME:
-            continuation = applyAssumeInstruction(instruction.asAssume());
-            break;
-          case CHECK_CAST:
-            continuation = applyCheckCastInstruction(instruction.asCheckCast());
-            break;
-          case CONST_NUMBER:
-            continuation = applyConstNumber(instruction.asConstNumber());
-            break;
-          case CONST_STRING:
-            continuation = applyConstString(instruction.asConstString());
-            break;
-          case GOTO:
-            continuation = applyGotoInstruction(instruction.asGoto());
-            break;
-          case INSTANCE_PUT:
-            continuation = applyInstancePut(instruction.asInstancePut());
-            break;
-          case INVOKE_DIRECT:
-          case INVOKE_INTERFACE:
-          case INVOKE_STATIC:
-          case INVOKE_SUPER:
-          case INVOKE_VIRTUAL:
-            continuation = applyInvokeMethodInstruction(instruction.asInvokeMethod());
-            break;
-          case RETURN:
-            continuation = applyReturnInstruction(instruction.asReturn());
-            break;
-          case THROW:
-            continuation = applyThrowInstruction(instruction.asThrow());
-            break;
-          default:
-            continuation = applyUnhandledInstruction();
-            break;
-        }
-        if (continuation.isAbort()) {
-          return continuation;
-        }
-        if (continuation.isBreak()) {
-          break;
-        }
-      }
-      return AnalysisContinuation.CONTINUE;
-    }
-
-    // TODO(b/339210038): When adding support for non-linear control flow, we need to implement
-    //  backtracking of this (i.e., we should remove the out value from the object aliases again).
-    private AnalysisContinuation applyAssumeInstruction(Assume assume) {
-      if (isMaybeInstance(assume.src())) {
-        addInstanceAlias(assume.outValue());
-      }
-      return AnalysisContinuation.CONTINUE;
-    }
-
-    // TODO(b/339210038): When adding support for non-linear control flow, we need to implement
-    //  backtracking of this (i.e., we should remove the out value from the object aliases again).
-    private AnalysisContinuation applyCheckCastInstruction(CheckCast checkCast) {
-      if (isMaybeInstance(checkCast.object())) {
-        addInstanceAlias(checkCast.outValue());
-      }
-      // If the instance has escaped to the heap and this check-cast instruction throws, then it is
-      // possible that the instance is retrieved from the heap and all fields are read.
-      return markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped();
-    }
-
-    private AnalysisContinuation applyConstNumber(ConstNumber unusedConstNumber) {
-      return AnalysisContinuation.CONTINUE;
-    }
-
-    private AnalysisContinuation applyConstString(ConstString unusedConstString) {
-      return AnalysisContinuation.CONTINUE;
-    }
-
-    private AnalysisContinuation applyGotoInstruction(Goto gotoInstruction) {
-      BasicBlock targetBlock = gotoInstruction.getTarget();
-      if (isBlockOnStack(targetBlock)) {
-        // Bail out in case of cycles.
-        return markRemainingFieldsAsMaybeReadBeforeWritten();
-      } else {
-        // Continue exploration into the successor block.
-        worklist.addIgnoringSeenSet(new ProcessBlockWorkItem(targetBlock));
-        return AnalysisContinuation.CONTINUE;
-      }
-    }
-
-    private AnalysisContinuation applyInstancePut(InstancePut instancePut) {
-      // If the instance has escaped and this instance-put instruction can throw, then the program
-      // can get the instance from the heap and read any field. Give up in this case.
-      if (isEscaped() && instancePut.instructionInstanceCanThrow(appView, code.context())) {
-        return markRemainingFieldsAsMaybeReadBeforeWritten();
-      }
-
-      // Record if this is a definite write to one of the fields of interest.
-      if (isDefinitelyInstance(instancePut.object())) {
-        ProgramField resolvedField =
-            instancePut.resolveField(appView, code.context()).getProgramField();
-        if (resolvedField != null && fields.contains(resolvedField)) {
-          addWrittenBeforeRead(resolvedField);
-        }
-
-        // If all fields of interest are written before read, then stop the exploration of the
-        // current program path (but continue to explore any program paths from previous unexplored
-        // branches).
-        if (fields.allMatch(self::isWrittenBeforeRead)) {
-          return AnalysisContinuation.BREAK;
-        }
-      }
-
-      // Record if the instance has escaped as a result of this instance-put.
-      if (!isEscaped() && isMaybeInstance(instancePut.value())) {
-        setEscaped(instancePut);
-      }
-      return AnalysisContinuation.CONTINUE;
-    }
-
-    private AnalysisContinuation applyInvokeMethodInstruction(InvokeMethod invoke) {
-      // Allow calls to java.lang.Object.<init>().
-      // TODO(b/339210038): Generalize this to other constructors.
-      if (invoke.isInvokeConstructor(dexItemFactory)
-          && isDefinitelyInstance(invoke.getFirstArgument())) {
-        DexClassAndMethod resolvedMethod =
-            invoke.resolveMethod(appView, code.context()).getResolutionPair();
-        if (resolvedMethod != null
-            && resolvedMethod
-                .getReference()
-                .isIdenticalTo(dexItemFactory.objectMembers.constructor)) {
-          return AnalysisContinuation.CONTINUE;
-        }
-      }
-
-      // Conservatively treat calls as reading any field if the receiver has escaped or is escaping.
-      if (!isEscaped()
-          && invoke.hasInValueThatMatches(self::isMaybeInstance)
-          && invoke.instructionMayHaveSideEffects(appView, code.context())) {
-        setEscaped(invoke);
-      }
-
-      if (isEscaped()) {
-        return markRemainingFieldsAsMaybeReadBeforeWritten();
-      }
-
-      // Otherwise, this is a call to a method where none of the arguments is an alias of the
-      // instance, and the instance has not escaped. Therefore, this call cannot read any of fields
-      // from the instance.
-      return AnalysisContinuation.CONTINUE;
-    }
-
-    private AnalysisContinuation applyReturnInstruction(Return unusedReturnInstruction) {
-      return markRemainingFieldsAsMaybeReadBeforeWritten();
-    }
-
-    private AnalysisContinuation applyThrowInstruction(Throw unusedThrowInstruction) {
-      return markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped();
-    }
-
-    private AnalysisContinuation applyUnhandledInstruction() {
-      return markRemainingFieldsAsMaybeReadBeforeWritten();
-    }
-
-    AnalysisContinuation markRemainingFieldsAsMaybeReadBeforeWritten() {
-      for (ProgramField field : fields) {
-        if (!isWrittenBeforeRead(field)) {
-          AnalysisContinuation continuation = acceptFieldMaybeReadBeforeWrite(field);
-          assert continuation.isAbortOrContinue();
-          if (continuation.isAbort()) {
-            return continuation;
-          }
-        }
-      }
-      // At this point we could also CONTINUE, but we check if the fields of interest have become
-      // empty as a result of concurrent modification.
-      return AnalysisContinuation.abortIf(fields.isEmpty());
-    }
-
-    AnalysisContinuation markRemainingFieldsAsMaybeReadBeforeWrittenIfInstanceIsEscaped() {
-      if (isEscaped()) {
-        return markRemainingFieldsAsMaybeReadBeforeWritten();
-      }
-      return AnalysisContinuation.CONTINUE;
-    }
-  }
-
-  class InitialWorkItem extends WorkItem {
-
-    @Override
-    TraversalContinuation<?, ?> process() {
-      // We start the analysis from the unique constructor invoke instead of from the NewInstance
-      // instruction, since no instructions before the constructor call can read any fields from the
-      // uninitialized this.
-      // TODO(b/339210038): In principle it may be possible for the NewInstance value to flow into a
-      //  phi before the unique constructor invoke. If this happens we would not record the phi as
-      //  an alias when starting the analysis from the invoke-direct.
-      InvokeDirect uniqueConstructorInvoke =
-          getNewInstance().getUniqueConstructorInvoke(dexItemFactory);
-      if (uniqueConstructorInvoke == null) {
-        return markRemainingFieldsAsMaybeReadBeforeWritten().toTraversalContinuation();
-      }
-      BasicBlock block = uniqueConstructorInvoke.getBlock();
-      // TODO(b/339210038): Maybe allow exceptional control flow.
-      if (block.hasCatchHandlers()) {
-        return markRemainingFieldsAsMaybeReadBeforeWritten().toTraversalContinuation();
-      }
-      addBlockToStack(block);
-      addInstanceAlias(getNewInstance().outValue());
-      BasicBlockInstructionIterator instructionIterator = block.iterator(uniqueConstructorInvoke);
-      // Start the analysis from the invoke-direct instruction. This is important if we can tell
-      // that the constructor definitely writes some fields.
-      instructionIterator.previous();
-      return applyInstructions(instructionIterator).toTraversalContinuation();
-    }
-  }
-
-  class ProcessBlockWorkItem extends WorkItem {
-
-    private final BasicBlock block;
-
-    ProcessBlockWorkItem(BasicBlock block) {
-      this.block = block;
-    }
-
-    @Override
-    TraversalContinuation<?, ?> process() {
-      // TODO(b/339210038): Maybe allow exceptional control flow.
-      if (block.hasCatchHandlers()) {
-        return TraversalContinuation.breakIf(
-            markRemainingFieldsAsMaybeReadBeforeWritten().isAbort());
-      }
-      addBlockToStack(block);
-      applyPhis(block);
-      AnalysisContinuation continuation = applyInstructions(block.iterator());
-      assert continuation.isAbortOrContinue();
-      return TraversalContinuation.breakIf(continuation.isAbort());
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java
deleted file mode 100644
index 7e24c03..0000000
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/FieldReadBeforeWriteDfsAnalysisState.java
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.optimize.argumentpropagation.propagation;
-
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.collections.ProgramFieldSet;
-import com.google.common.collect.Sets;
-import java.util.Set;
-
-/**
- * The state we track during the field-maybe-read-before-write/field-never-read-before-written
- * analysis.
- */
-public class FieldReadBeforeWriteDfsAnalysisState {
-
-  // The current allocation we are analyzing.
-  private final NewInstance newInstance;
-
-  // The first instruction on the current program path starting from the `newInstance` instruction
-  // from which the `newInstance` value escapes.
-  private Instruction escape = null;
-
-  // The set of values that *may* be aliases of the `newInstance` value.
-  private final Set<Value> instanceAliases = Sets.newIdentityHashSet();
-
-  // The set of blocks on the current program path.
-  private final Set<BasicBlock> stack = Sets.newIdentityHashSet();
-
-  // The set of fields that are guaranteed to be written before they are read on the current program
-  // path.
-  private final ProgramFieldSet writtenBeforeRead = ProgramFieldSet.create();
-
-  FieldReadBeforeWriteDfsAnalysisState(NewInstance newInstance) {
-    this.newInstance = newInstance;
-  }
-
-  void addInstanceAlias(Value instanceAlias) {
-    boolean changed = instanceAliases.add(instanceAlias);
-    assert changed;
-  }
-
-  void addBlockToStack(BasicBlock block) {
-    boolean changed = stack.add(block);
-    assert changed;
-  }
-
-  void addWrittenBeforeRead(ProgramField field) {
-    writtenBeforeRead.add(field);
-  }
-
-  NewInstance getNewInstance() {
-    return newInstance;
-  }
-
-  boolean isBlockOnStack(BasicBlock block) {
-    return stack.contains(block);
-  }
-
-  boolean isEscaped() {
-    return escape != null;
-  }
-
-  boolean isDefinitelyInstance(Value value) {
-    return value.getAliasedValue() == newInstance.outValue();
-  }
-
-  boolean isMaybeInstance(Value value) {
-    return instanceAliases.contains(value);
-  }
-
-  boolean isWrittenBeforeRead(ProgramField field) {
-    return writtenBeforeRead.contains(field);
-  }
-
-  void setEscaped(Instruction escape) {
-    assert !isEscaped();
-    this.escape = escape;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index 81e3424..d62328d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -46,7 +46,7 @@
   final AppView<AppInfoWithLiveness> appView;
   final Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers;
   final IRConverter converter;
-  protected final FieldStateCollection fieldStates;
+  final FieldStateCollection fieldStates;
   final MethodStateCollectionByReference methodStates;
 
   public InFlowPropagator(
@@ -113,15 +113,12 @@
 
   private Map<FlowGraph, Deque<FlowGraphNode>> includeDefaultValuesInFieldStates(
       List<FlowGraph> flowGraphs, ExecutorService executorService) throws ExecutionException {
-    DefaultFieldValueJoiner joiner = createDefaultFieldValueJoiner(flowGraphs);
+    DefaultFieldValueJoiner joiner =
+        new DefaultFieldValueJoiner(
+            appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs);
     return joiner.joinDefaultFieldValuesForFieldsWithReadBeforeWrite(executorService);
   }
 
-  protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(List<FlowGraph> flowGraphs) {
-    return new DefaultFieldValueJoiner(
-        appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs);
-  }
-
   private void processFlowGraphs(List<FlowGraph> flowGraphs, ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
index 22ed377..8d7498c 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposeMethodProcessor.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
@@ -34,8 +33,6 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.DefaultFieldValueJoiner;
-import com.android.tools.r8.optimize.argumentpropagation.propagation.FlowGraph;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InFlowPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
@@ -45,8 +42,6 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -97,22 +92,7 @@
 
     InFlowPropagator inFlowPropagator =
         new InFlowPropagator(
-            appView, null, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates()) {
-
-          @Override
-          protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(
-              List<FlowGraph> flowGraphs) {
-            return new DefaultFieldValueJoiner(appView, null, fieldStates, flowGraphs) {
-
-              @Override
-              protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
-                // We do not rely on the optimization of any fields in the Composable optimization
-                // pass.
-                return Collections.emptyMap();
-              }
-            };
-          }
-        };
+            appView, null, converter, codeScanner.getFieldStates(), codeScanner.getMethodStates());
     inFlowPropagator.run(executorService);
 
     ArgumentPropagatorOptimizationInfoPopulator optimizationInfoPopulator =
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java
index fa0bdfd..205bd4b 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerScanner.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.lightir.LirInstructionView;
 import com.android.tools.r8.lightir.LirOpcodeUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -47,7 +48,8 @@
     singleCallerMethodCandidates.removeIf(
         (callee, caller) ->
             callee.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
-                || !appView.getKeepInfo(callee).isSingleCallerInliningAllowed(options));
+                || !appView.getKeepInfo(callee).isSingleCallerInliningAllowed(options)
+                || !AndroidApiLevelUtils.isApiSafeForInlining(caller, callee, appView.options()));
     return traceInstructions(singleCallerMethodCandidates, executorService);
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
index bd53f9a..b996483 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ApiReferenceStubber;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -131,7 +132,7 @@
 
   @Override
   public void registerTypeReference(DexType type) {
-    // Type references are OK as long as we do not have a use on them
+    // Type references are OK as long as we do not have a use on them.
   }
 
   @Override
@@ -141,7 +142,9 @@
 
   @Override
   public void registerExceptionGuard(DexType guard) {
-    setMaxApiReferenceLevel(guard);
+    // Type references as exception guard are OK for stubbed exception guards as they are always
+    // present at runtime.
+    setMaxApiReferenceLevelForGuard(guard);
   }
 
   @Override
@@ -170,6 +173,17 @@
     }
   }
 
+  private void setMaxApiReferenceLevelForGuard(DexType type) {
+    if (isEnabled) {
+      ComputedApiLevel computedApiLevel = apiLevelCompute.computeApiLevelForLibraryReference(type);
+      if (ApiReferenceStubber.isNeverStubbedType(type, appInfoWithClassHierarchy.dexItemFactory())
+          || appInfoWithClassHierarchy.options().canHaveDalvikCatchHandlerVerificationBug()
+          || computedApiLevel.isUnknownApiLevel()) {
+        maxApiReferenceLevel = maxApiReferenceLevel.max(computedApiLevel);
+      }
+    }
+  }
+
   @SuppressWarnings("HidingField")
   public ComputedApiLevel getMaxApiReferenceLevel() {
     return maxApiReferenceLevel;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
index c48ceb0..d7369fbd 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -22,7 +22,7 @@
   public static EnqueuerDeferredTracing create(
       AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer, Mode mode) {
     InternalOptions options = appView.options();
-    if (!options.isShrinking() || !options.enableEnqueuerDeferredTracing) {
+    if (!options.isShrinking()) {
       return empty();
     }
     if (!options.isOptimizedResourceShrinking()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 8eac490..1c3889d 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -82,6 +82,11 @@
       ProgramMethod context,
       FieldAccessKind accessKind,
       FieldAccessMetadata metadata) {
+    if (!fieldReference.getType().isPrimitiveType()
+        && !options.getTestingOptions().enableEnqueuerDeferredTracingForReferenceFields) {
+      return false;
+    }
+
     ProgramField field = resolutionResult.getSingleProgramField();
     if (field == null) {
       return false;
@@ -97,7 +102,7 @@
     }
 
     // If the access is from a reachability sensitive method, then bail out.
-    if (context.getHolder().getOrComputeReachabilitySensitive(appView)) {
+    if (context.getHolder().isReachabilitySensitive()) {
       return enqueueDeferredEnqueuerActions(field);
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
index 7eb81ce..30e9735 100644
--- a/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
@@ -67,8 +67,12 @@
                         .resolveMethodOnLegacy(
                             clazz, appView.dexItemFactory().objectMembers.finalize)
                         .asSingleResolution();
-                if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
-                  return TraversalContinuation.doBreak();
+                if (resolution != null) {
+                  DexType resolvedType = resolution.getResolvedHolder().getType();
+                  if (resolvedType.isNotIdenticalTo(appView.dexItemFactory().objectType)
+                      && resolvedType.isNotIdenticalTo(appView.dexItemFactory().enumType)) {
+                    return TraversalContinuation.doBreak();
+                  }
                 }
               }
               return TraversalContinuation.doContinue();
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java
index e33a5fa..3354e65 100644
--- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java
+++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.graph.NestHostClassAttribute;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 
@@ -58,7 +59,8 @@
         MethodCollectionFactory.fromMethods(
             createDirectMethods(dexItemFactory), createVirtualMethods(dexItemFactory)),
         dexItemFactory.getSkipNameValidationForTesting(),
-        DexProgramClass::invalidChecksumRequest);
+        DexProgramClass::invalidChecksumRequest,
+        ReachabilitySensitiveValue.DISABLED);
   }
 
   private static DexEncodedField[] createInstanceFields() {
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
index f6d1bf4..a70f457 100644
--- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
+++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.code.MonitorType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
 import java.util.ArrayDeque;
@@ -80,7 +81,8 @@
         MethodCollectionFactory.fromMethods(
             createDirectMethods(dexItemFactory), createVirtualMethods(dexItemFactory)),
         dexItemFactory.getSkipNameValidationForTesting(),
-        DexProgramClass::invalidChecksumRequest);
+        DexProgramClass::invalidChecksumRequest,
+        ReachabilitySensitiveValue.DISABLED);
   }
 
   private static DexEncodedField[] createInstanceFields(DexItemFactory dexItemFactory) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 068a1a7..3340e46 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.RecordComponentInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -214,7 +215,8 @@
                 DexEncodedMethod.EMPTY_ARRAY,
                 factory.getSkipNameValidationForTesting(),
                 c -> checksum,
-                null);
+                null,
+                ReachabilitySensitiveValue.DISABLED);
     if (useSortedMethodBacking) {
       clazz.getMethodCollection().useSortedBacking();
     }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java
index c5cb640..f6c7269 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpBase.java
@@ -29,6 +29,11 @@
         .accept(new Object[] {androidPlatformBuild});
   }
 
+  static void setIsolatedSplits(Object builder, boolean isolatedSplits) {
+    getReflectiveBuilderMethod(builder, "setEnableIsolatedSplits", boolean.class)
+        .accept(new Object[] {isolatedSplits});
+  }
+
   static void addArtProfilesForRewriting(Object builder, Map<Path, Path> artProfileFiles) {
     try {
       Class<?> artProfileProviderClass =
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 6760583..cd01544 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -35,6 +35,8 @@
  */
 public class CompileDumpCompatR8 extends CompileDumpBase {
 
+  private static final String ISOLATED_SPLITS_FLAG = "--isolated-splits";
+
   private static final List<String> VALID_OPTIONS =
       Arrays.asList(
           "--classfile",
@@ -42,7 +44,8 @@
           "--debug",
           "--release",
           "--enable-missing-library-api-modeling",
-          "--android-platform-build");
+          "--android-platform-build",
+          ISOLATED_SPLITS_FLAG);
 
   private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
       Arrays.asList(
@@ -92,6 +95,7 @@
     int threads = -1;
     boolean enableMissingLibraryApiModeling = false;
     boolean androidPlatformBuild = false;
+    boolean isolatedSplits = false;
     for (int i = 0; i < args.length; i++) {
       String option = args[i];
       if (VALID_OPTIONS.contains(option)) {
@@ -122,6 +126,9 @@
           case "--android-platform-build":
             androidPlatformBuild = true;
             break;
+          case ISOLATED_SPLITS_FLAG:
+            isolatedSplits = true;
+            break;
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
@@ -225,6 +232,7 @@
     addArtProfilesForRewriting(commandBuilder, artProfileFiles);
     addStartupProfileProviders(commandBuilder, startupProfileFiles);
     setAndroidPlatformBuild(commandBuilder, androidPlatformBuild);
+    setIsolatedSplits(commandBuilder, isolatedSplits);
     setEnableExperimentalMissingLibraryApiModeling(commandBuilder, enableMissingLibraryApiModeling);
     if (desugaredLibJson != null) {
       commandBuilder.addDesugaredLibraryConfiguration(readAllBytesJava7(desugaredLibJson));
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 bbfe77c..d68f123 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -737,9 +737,6 @@
   // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
   public boolean processCovariantReturnTypeAnnotations = true;
 
-  public boolean enableEnqueuerDeferredTracing =
-      System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null;
-
   public boolean loadAllClassDefinitions = false;
 
   // Whether or not to check for valid multi-dex builds.
@@ -924,12 +921,12 @@
 
   public boolean debug = false;
 
-  public boolean shouldCompileMethodInDebugMode(AppView<?> appView, ProgramMethod method) {
-    return debug || method.getOrComputeReachabilitySensitive(appView);
+  public boolean shouldCompileMethodInDebugMode(ProgramMethod method) {
+    return debug || method.isReachabilitySensitive();
   }
 
   public boolean shouldCompileMethodInReleaseMode(AppView<?> appView, ProgramMethod method) {
-    return !shouldCompileMethodInDebugMode(appView, method);
+    return !shouldCompileMethodInDebugMode(method);
   }
 
   private final AccessModifierOptions accessModifierOptions = new AccessModifierOptions(this);
@@ -2367,6 +2364,8 @@
     public boolean enableBridgeHoistingToSharedSyntheticSuperclass = false;
     public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
+    public boolean enableEnqueuerDeferredTracingForReferenceFields =
+        System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableLegacyClassDefOrdering =
         System.getProperty("com.android.tools.r8.enableLegacyClassDefOrdering") != null;
diff --git a/src/main/java/com/android/tools/r8/utils/ReachabilitySensitiveValue.java b/src/main/java/com/android/tools/r8/utils/ReachabilitySensitiveValue.java
new file mode 100644
index 0000000..efdddc8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ReachabilitySensitiveValue.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+public enum ReachabilitySensitiveValue {
+  DISABLED,
+  ENABLED;
+
+  public static ReachabilitySensitiveValue fromBoolean(boolean enabled) {
+    return enabled ? ENABLED : DISABLED;
+  }
+
+  public boolean isEnabled() {
+    return this == ENABLED;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
index 891593a..c778df3 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldSet.java
@@ -8,15 +8,14 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramField;
-import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 public class ProgramFieldSet implements Iterable<ProgramField> {
@@ -55,10 +54,6 @@
     backing.putAll(fields.backing);
   }
 
-  public boolean allMatch(Predicate<? super ProgramField> predicate) {
-    return Iterables.all(this, predicate);
-  }
-
   public boolean createAndAdd(DexProgramClass clazz, DexEncodedField definition) {
     return add(new ProgramField(clazz, definition));
   }
diff --git a/src/test/examplesJava11/com/android/tools/r8/AlwaysInline.java b/src/test/examplesJava11/com/android/tools/r8/AlwaysInline.java
new file mode 100644
index 0000000..f76ecf0
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/AlwaysInline.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface AlwaysInline {}
diff --git a/src/test/examplesJava11/com/android/tools/r8/NeverInline.java b/src/test/examplesJava11/com/android/tools/r8/NeverInline.java
index 3c69ab7..f41f718 100644
--- a/src/test/examplesJava11/com/android/tools/r8/NeverInline.java
+++ b/src/test/examplesJava11/com/android/tools/r8/NeverInline.java
@@ -1,11 +1,14 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8;
 
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+@Retention(RetentionPolicy.CLASS)
 @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
 public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java b/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java
similarity index 77%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
rename to src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java
index e4847bd..52960eb 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
+++ b/src/test/examplesJava11/nesthostexample/NestAttributesUpdateTest.java
@@ -2,10 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.nestaccesscontrol;
+package nesthostexample;
 
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.PACKAGE_NAME;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertNotNull;
@@ -33,20 +31,19 @@
 
 @RunWith(Parameterized.class)
 public class NestAttributesUpdateTest extends TestBase {
+  private static final String PACKAGE_NAME = "nesthostexample.";
+
+  private static final Class<?> MERGING_OUTER_CLASS = BasicNestHostClassMerging.class;
+  private static final Class<?> PRUNING_OUTER_CLASS = BasicNestHostTreePruning.class;
+  private static final String MERGING_EXPECTED_RESULT = StringUtils.lines("OuterMiddleInner");
+  private static final String PRUNING_EXPECTED_RESULT = StringUtils.lines("NotPruned");
 
   @Parameter(0)
   public TestParameters parameters;
 
-  private final String MERGING_OUTER_CLASS = "BasicNestHostClassMerging";
-  private final String PRUNING_OUTER_CLASS = "BasicNestHostTreePruning";
-  private final String MERGING_EXPECTED_RESULT = StringUtils.lines("OuterMiddleInner");
-  private final String PRUNING_EXPECTED_RESULT = StringUtils.lines("NotPruned");
-
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
-        .build();
+    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build();
   }
 
   @Test
@@ -62,18 +59,17 @@
   @Test
   public void testClassMergingNestHostRemoval() throws Exception {
     testNestAttributesCorrect(
-        MERGING_OUTER_CLASS + "$MiddleOuter",
+        BasicNestHostClassMerging.MiddleOuter.class,
         MERGING_OUTER_CLASS,
         MERGING_EXPECTED_RESULT,
         2,
-        builder -> {
-          builder.addOptionsModification(
-              internalOptions -> {
-                // The test makes an invoke to StringConcatFactory which is not known to DEX and
-                // we therefore fail to merge the classes.
-                internalOptions.apiModelingOptions().enableApiCallerIdentification = false;
-              });
-        });
+        builder ->
+            builder.addOptionsModification(
+                internalOptions -> {
+                  // The test makes an invoke to StringConcatFactory which is not known to DEX and
+                  // we therefore fail to merge the classes.
+                  internalOptions.apiModelingOptions().enableApiCallerIdentification = false;
+                }));
   }
 
   @Test
@@ -89,7 +85,7 @@
   @Test
   public void testTreePruningNestHostRemoval() throws Exception {
     testNestAttributesCorrect(
-        PRUNING_OUTER_CLASS + "$Pruned",
+        BasicNestHostTreePruning.Pruned.class,
         PRUNING_OUTER_CLASS,
         PRUNING_EXPECTED_RESULT,
         1,
@@ -97,22 +93,22 @@
   }
 
   public void testNestAttributesCorrect(
-      String mainClassName,
-      String outerNestName,
+      Class<?> mainClass,
+      Class<?> outerNestClass,
       String expectedResult,
       int expectedNumClassesLeft,
       ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer)
       throws Exception {
     testNestAttributesCorrect(
-        mainClassName,
-        outerNestName,
+        mainClass,
+        outerNestClass,
         expectedResult,
         true,
         expectedNumClassesLeft,
         testBuilderConsumer);
     testNestAttributesCorrect(
-        mainClassName,
-        outerNestName,
+        mainClass,
+        outerNestClass,
         expectedResult,
         false,
         expectedNumClassesLeft,
@@ -120,23 +116,22 @@
   }
 
   public void testNestAttributesCorrect(
-      String mainClassName,
-      String outerNestName,
+      Class<?> mainClass,
+      Class<?> outerNestClass,
       String expectedResult,
       boolean minification,
       int expectedNumClassesLeft,
       ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer)
       throws Exception {
-    String actualMainClassName = PACKAGE_NAME + mainClassName;
     testForR8(parameters.getBackend())
-        .addKeepMainRule(actualMainClassName)
+        .addKeepMainRule(mainClass)
         .minification(minification)
         .addOptionsModification(
             options -> {
               // Disable optimizations else additional classes are removed since they become unused.
               options.enableClassInlining = false;
             })
-        .addProgramFiles(classesMatching(outerNestName))
+        .addProgramClassesAndInnerClasses(outerNestClass)
         .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp))
         .addKeepPackageNamesRule("nesthostexample")
         .addInliningAnnotations()
@@ -147,7 +142,7 @@
               assertEquals(expectedNumClassesLeft, inspector.allClasses().size());
               assertNestAttributesCorrect(inspector);
             })
-        .run(parameters.getRuntime(), actualMainClassName)
+        .run(parameters.getRuntime(), mainClass)
         .assertSuccessWithOutput(expectedResult);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java b/src/test/examplesJava11/nesthostexample/NestClassMergingTest.java
similarity index 65%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
rename to src/test/examplesJava11/nesthostexample/NestClassMergingTest.java
index 322fb9b..273b7ec 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
+++ b/src/test/examplesJava11/nesthostexample/NestClassMergingTest.java
@@ -1,17 +1,16 @@
-package com.android.tools.r8.desugar.nestaccesscontrol;
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
 
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.PACKAGE_NAME;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching;
+package nesthostexample;
 
 import com.android.tools.r8.Jdk9TestUtils;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -25,10 +24,10 @@
   @Parameter(0)
   public TestParameters parameters;
 
-  private final String NEST_MAIN_CLASS = PACKAGE_NAME + "NestHostInlining";
-  private final String NEST_SUBCLASS_MAIN_CLASS = PACKAGE_NAME + "NestHostInliningSubclasses";
-  private final String OUTSIDE_WITH_ACCESS_MAIN_CLASS = PACKAGE_NAME + "OutsideInliningWithAccess";
-  private final String OUTSIDE_NO_ACCESS_MAIN_CLASS = PACKAGE_NAME + "OutsideInliningNoAccess";
+  private final Class<?> NEST_MAIN_CLASS = NestHostInlining.class;
+  private final Class<?> NEST_SUBCLASS_MAIN_CLASS = NestHostInliningSubclasses.class;
+  private final Class<?> OUTSIDE_WITH_ACCESS_MAIN_CLASS = OutsideInliningWithAccess.class;
+  private final Class<?> OUTSIDE_NO_ACCESS_MAIN_CLASS = OutsideInliningNoAccess.class;
   private final String NEST_MAIN_EXPECTED_RESULT =
       StringUtils.lines("inlining", "InnerNoPrivAccess");
   private final String NEST_SUBCLASS_MAIN_EXPECTED_RESULT =
@@ -40,20 +39,19 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
-        .build();
+    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build();
   }
 
   @Test
   public void testClassMergeAcrossTwoNests() throws Exception {
     // Potentially merge classes from one nest with classes from another nest.
     testClassMergeAcrossNest(
-        new String[] {NEST_MAIN_CLASS}, new String[] {NEST_MAIN_EXPECTED_RESULT});
+        new Class<?>[] {NEST_MAIN_CLASS}, new String[] {NEST_MAIN_EXPECTED_RESULT});
     testClassMergeAcrossNest(
-        new String[] {NEST_SUBCLASS_MAIN_CLASS}, new String[] {NEST_SUBCLASS_MAIN_EXPECTED_RESULT});
+        new Class<?>[] {NEST_SUBCLASS_MAIN_CLASS},
+        new String[] {NEST_SUBCLASS_MAIN_EXPECTED_RESULT});
     testClassMergeAcrossNest(
-        new String[] {NEST_MAIN_CLASS, NEST_SUBCLASS_MAIN_CLASS},
+        new Class<?>[] {NEST_MAIN_CLASS, NEST_SUBCLASS_MAIN_CLASS},
         new String[] {NEST_MAIN_EXPECTED_RESULT, NEST_SUBCLASS_MAIN_EXPECTED_RESULT});
   }
 
@@ -61,7 +59,7 @@
   public void testClassMergeAcrossNestAndNonNest() throws Exception {
     // Potentially merge classes from a nest with non nest classes.
     testClassMergeAcrossNest(
-        new String[] {
+        new Class<?>[] {
           NEST_MAIN_CLASS, OUTSIDE_NO_ACCESS_MAIN_CLASS, OUTSIDE_WITH_ACCESS_MAIN_CLASS
         },
         new String[] {
@@ -70,22 +68,23 @@
           OUTSIDE_WITH_ACCESS_MAIN_EXPECTED_RESULT
         });
     testClassMergeAcrossNest(
-        new String[] {OUTSIDE_NO_ACCESS_MAIN_CLASS},
+        new Class<?>[] {OUTSIDE_NO_ACCESS_MAIN_CLASS},
         new String[] {OUTSIDE_NO_ACCESS_MAIN_EXPECTED_RESULT});
     testClassMergeAcrossNest(
-        new String[] {OUTSIDE_WITH_ACCESS_MAIN_CLASS},
+        new Class<?>[] {OUTSIDE_WITH_ACCESS_MAIN_CLASS},
         new String[] {OUTSIDE_WITH_ACCESS_MAIN_EXPECTED_RESULT});
   }
 
-  public void testClassMergeAcrossNest(String[] mainClasses, String[] expectedResults)
+  public void testClassMergeAcrossNest(Class<?>[] mainClasses, String[] expectedResults)
       throws Exception {
-    List<Path> bothNestsAndOutsideClassToCompile = classesMatching("Inlining");
-    R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend());
-    for (String clazz : mainClasses) {
-      r8FullTestBuilder.addKeepMainRule(clazz);
-    }
     R8TestCompileResult compileResult =
-        r8FullTestBuilder
+        testForR8(parameters.getBackend())
+            .apply(
+                b -> {
+                  for (Class<?> clazz : mainClasses) {
+                    b.addKeepMainRule(clazz);
+                  }
+                })
             .addOptionsModification(
                 options -> {
                   // Disable optimizations else additional classes are removed since they become
@@ -94,7 +93,12 @@
                   options.enableNestReduction = false;
                 })
             .enableInliningAnnotations()
-            .addProgramFiles(bothNestsAndOutsideClassToCompile)
+            .addProgramClassesAndInnerClasses(
+                List.of(
+                    NEST_MAIN_CLASS,
+                    NEST_SUBCLASS_MAIN_CLASS,
+                    OUTSIDE_WITH_ACCESS_MAIN_CLASS,
+                    OUTSIDE_NO_ACCESS_MAIN_CLASS))
             .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp))
             .addKeepPackageNamesRule("nesthostexample")
             .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java b/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java
similarity index 65%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java
rename to src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java
index 5b59d1d..70c0120 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java
+++ b/src/test/examplesJava11/nesthostexample/NestConstructorRemovedArgTest.java
@@ -2,18 +2,15 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.nestaccesscontrol;
+package nesthostexample;
 
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesOfNest;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass;
+import static nesthostexample.BasicNestHostWithInnerClassConstructors.getExpectedResult;
 
 import com.android.tools.r8.Jdk9TestUtils;
 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.ToolHelper.DexVm;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -22,6 +19,8 @@
 @RunWith(Parameterized.class)
 public class NestConstructorRemovedArgTest extends TestBase {
 
+  private static final Class<?> MAIN_CLASS = BasicNestHostWithInnerClassConstructors.class;
+
   public NestConstructorRemovedArgTest(TestParameters parameters) {
     this.parameters = parameters;
   }
@@ -32,8 +31,7 @@
   public static TestParametersCollection data() {
     return getTestParameters()
         .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
-        .withDexRuntime(DexVm.Version.first())
-        .withDexRuntime(DexVm.Version.last())
+        .withDexRuntimes()
         .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
         .enableApiLevelsForCf()
         .build();
@@ -42,33 +40,31 @@
   @Test
   public void testRemoveArgConstructorNestsR8() throws Exception {
     parameters.assumeR8TestParameters();
-    String nestID = "constructors";
     testForR8(parameters.getBackend())
-        .addKeepMainRule(getMainClass(nestID))
+        .addKeepMainRule(MAIN_CLASS)
         .addDontObfuscate()
         .setMinApi(parameters)
         .addOptionsModification(options -> options.enableClassInlining = false)
-        .addProgramFiles(classesOfNest(nestID))
+        .addProgramClassesAndInnerClasses(MAIN_CLASS)
         .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp))
         .compile()
-        .run(parameters.getRuntime(), getMainClass(nestID))
-        .assertSuccessWithOutput(getExpectedResult(nestID));
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutputLines(getExpectedResult());
   }
 
   @Test
   public void testRemoveArgConstructorNestsR8NoTreeShaking() throws Exception {
     parameters.assumeR8TestParameters();
-    String nestID = "constructors";
     testForR8(parameters.getBackend())
         .noTreeShaking()
-        .addKeepMainRule(getMainClass(nestID))
+        .addKeepMainRule(MAIN_CLASS)
         .addDontObfuscate()
         .setMinApi(parameters)
         .addOptionsModification(options -> options.enableClassInlining = false)
-        .addProgramFiles(classesOfNest(nestID))
+        .addProgramClassesAndInnerClasses(MAIN_CLASS)
         .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp))
         .compile()
-        .run(parameters.getRuntime(), getMainClass(nestID))
-        .assertSuccessWithOutput(getExpectedResult(nestID));
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutputLines(getExpectedResult());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMemberPropagatedTest.java b/src/test/examplesJava11/nesthostexample/NestMemberPropagatedTest.java
similarity index 63%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMemberPropagatedTest.java
rename to src/test/examplesJava11/nesthostexample/NestMemberPropagatedTest.java
index 3e86e4f..dc1a1d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMemberPropagatedTest.java
+++ b/src/test/examplesJava11/nesthostexample/NestMemberPropagatedTest.java
@@ -2,11 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.nestaccesscontrol;
+package nesthostexample;
 
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass;
 import static junit.framework.TestCase.assertEquals;
 
 import com.android.tools.r8.TestBase;
@@ -15,8 +12,6 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.nio.file.Path;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,6 +20,8 @@
 @RunWith(Parameterized.class)
 public class NestMemberPropagatedTest extends TestBase {
 
+  private static final Class<?> MAIN_CLASS = NestPvtFieldPropagated.class;
+
   public NestMemberPropagatedTest(TestParameters parameters) {
     this.parameters = parameters;
   }
@@ -38,19 +35,15 @@
 
   @Test
   public void testPvtMemberPropagated() throws Exception {
-    List<Path> toCompile = classesMatching("NestPvtFieldPropagated");
     testForR8(parameters.getBackend())
-        .addKeepMainRule(getMainClass("memberPropagated"))
+        .addKeepMainRule(MAIN_CLASS)
         .addDontObfuscate()
-        .addOptionsModification(
-            options -> {
-              options.enableClassInlining = false;
-            })
-        .addProgramFiles(toCompile)
+        .addOptionsModification(options -> options.enableClassInlining = false)
+        .addProgramClassesAndInnerClasses(MAIN_CLASS)
         .compile()
         .inspect(this::assertMemberPropagated)
-        .run(parameters.getRuntime(), getMainClass("memberPropagated"))
-        .assertSuccessWithOutput(getExpectedResult("memberPropagated"));
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutputLines("toPropagateStatic");
   }
 
   private void assertMemberPropagated(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/examplesJava11/nesthostexample/NestMethodInlinedTest.java
similarity index 77%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
rename to src/test/examplesJava11/nesthostexample/NestMethodInlinedTest.java
index 48d4ca1..ef93446 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/examplesJava11/nesthostexample/NestMethodInlinedTest.java
@@ -2,11 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.nestaccesscontrol;
+package nesthostexample;
 
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertTrue;
 
@@ -14,12 +11,11 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.nio.file.Path;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,6 +24,19 @@
 @RunWith(Parameterized.class)
 public class NestMethodInlinedTest extends TestBase {
 
+  private static final Class<?> MAIN_CLASS = NestPvtMethodCallInlined.class;
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "nestPvtCallToInlineInner",
+          "nestPvtCallToInlineInnerInterface",
+          "notInlinedPvtCallInner",
+          "notInlinedPvtCallInnerInterface",
+          "notInlinedPvtCallInnerSub",
+          "notInlinedPvtCallInnerInterface",
+          "nestPvtCallToInlineInnerSub",
+          "nestPvtCallToInlineInner");
+
   public NestMethodInlinedTest(TestParameters parameters) {
     this.parameters = parameters;
   }
@@ -45,17 +54,16 @@
   @Test
   public void testReference() throws Exception {
     testForRuntime(parameters)
-        .addProgramFiles(classesMatching("NestPvtMethodCallInlined"))
-        .run(parameters.getRuntime(), getMainClass("pvtCallInlined"))
-        .assertSuccessWithOutput(getExpectedResult("pvtCallInlined"));
+        .addProgramClassesAndInnerClasses(MAIN_CLASS)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testPvtMethodCallInlined() throws Exception {
     parameters.assumeR8TestParameters();
-    List<Path> toCompile = classesMatching("NestPvtMethodCallInlined");
     testForR8(parameters.getBackend())
-        .addKeepMainRule(getMainClass("pvtCallInlined"))
+        .addKeepMainRule(MAIN_CLASS)
         .addDontObfuscate()
         .addOptionsModification(
             options -> {
@@ -63,13 +71,14 @@
               options.getVerticalClassMergerOptions().disable();
             })
         .enableInliningAnnotations()
+        .enableAlwaysInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
-        .addProgramFiles(toCompile)
+        .addProgramClassesAndInnerClasses(MAIN_CLASS)
         .compile()
         .inspect(this::assertMethodsInlined)
         .inspect(NestAttributesUpdateTest::assertNestAttributesCorrect)
-        .run(parameters.getRuntime(), getMainClass("pvtCallInlined"))
-        .assertSuccessWithOutput(getExpectedResult("pvtCallInlined"));
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   private void assertMethodsInlined(CodeInspector inspector) {
diff --git a/src/test/examplesJava11/nesthostexample/NestOnProgramOnClassPathTest.java b/src/test/examplesJava11/nesthostexample/NestOnProgramOnClassPathTest.java
new file mode 100644
index 0000000..d2e3d0c
--- /dev/null
+++ b/src/test/examplesJava11/nesthostexample/NestOnProgramOnClassPathTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package nesthostexample;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NestOnProgramOnClassPathTest extends TestBase {
+
+  public NestOnProgramOnClassPathTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  @Test
+  public void testD8MethodBridgesPresent() throws Exception {
+    parameters.assumeDexRuntime();
+    Class<?> nestHost = BasicNestHostWithInnerClassMethods.class;
+    // 1 inner class.
+    D8TestCompileResult singleInner =
+        compileClassesWithD8ProgramClasses(
+            nestHost, BasicNestHostWithInnerClassMethods.BasicNestedClass.class);
+    singleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 2));
+    // Outer class.
+    D8TestCompileResult host = compileClassesWithD8ProgramClasses(nestHost, nestHost);
+    host.inspect(inspector -> assertThisNumberOfBridges(inspector, 2));
+    // 2 inner classes.
+    D8TestCompileResult multipleInner =
+        compileClassesWithD8ProgramClasses(
+            NestHostExample.class,
+            NestHostExample.StaticNestMemberInner.class,
+            NestHostExample.StaticNestMemberInner.StaticNestMemberInnerInner.class);
+    multipleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 5));
+  }
+
+  @Test
+  public void testD8ConstructorBridgesPresent() throws Exception {
+    parameters.assumeDexRuntime();
+    Class<?> nestHost = BasicNestHostWithInnerClassConstructors.class;
+    D8TestCompileResult inner =
+        compileClassesWithD8ProgramClasses(
+            nestHost, BasicNestHostWithInnerClassConstructors.BasicNestedClass.class);
+    inner.inspect(
+        inspector -> {
+          assertThisNumberOfBridges(inspector, 3);
+          assertNestConstructor(inspector);
+        });
+    D8TestCompileResult host = compileClassesWithD8ProgramClasses(nestHost, nestHost);
+    host.inspect(
+        inspector -> {
+          assertThisNumberOfBridges(inspector, 1);
+          assertNestConstructor(inspector);
+        });
+  }
+
+  @Test
+  public void testD8ConstructorNestMergeCorrect() throws Exception {
+    // Multiple Nest Constructor classes have to be merged here.
+    parameters.assumeDexRuntime();
+    Class<?> nestHost = BasicNestHostWithInnerClassConstructors.class;
+    D8TestCompileResult inner =
+        compileClassesWithD8ProgramClasses(
+            nestHost, BasicNestHostWithInnerClassConstructors.BasicNestedClass.class);
+    D8TestCompileResult host = compileClassesWithD8ProgramClasses(nestHost, nestHost);
+    testForD8()
+        .addProgramFiles(inner.writeToZip(), host.writeToZip())
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), nestHost)
+        .assertSuccessWithOutputLines(BasicNestHostWithInnerClassConstructors.getExpectedResult());
+  }
+
+  private D8TestCompileResult compileClassesWithD8ProgramClasses(
+      Class<?> nestHost, Class<?>... classes) throws Exception {
+    return testForD8()
+        .setMinApi(parameters)
+        .addProgramClasses(classes)
+        .addClasspathClasses(nestHost.getNestMembers())
+        .compile();
+  }
+
+  private static void assertNestConstructor(CodeInspector inspector) {
+    assertTrue(inspector.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic));
+  }
+
+  private static void assertThisNumberOfBridges(CodeInspector inspector, int numBridges) {
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (!clazz.isSynthetic()) {
+        assertEquals(numBridges, clazz.allMethods(FoundMethodSubject::isSynthetic).size());
+      }
+    }
+  }
+}
diff --git a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
index 1e36fe1..9670e69 100644
--- a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
+++ b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
@@ -4,6 +4,7 @@
 
 package nesthostexample;
 
+import com.android.tools.r8.AlwaysInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverPropagateValue;
 
@@ -11,6 +12,7 @@
 
   public static class Inner {
 
+    @AlwaysInline
     public String methodWithPvtCallToInline() {
       return notInlinedPvtCall();
     }
@@ -21,6 +23,7 @@
       return "notInlinedPvtCallInner";
     }
 
+    @AlwaysInline
     private String nestPvtCallToInline() {
       return "nestPvtCallToInlineInner";
     }
@@ -28,6 +31,7 @@
 
   public interface InnerInterface {
 
+    @AlwaysInline
     default String methodWithPvtCallToInline() {
       return notInlinedPvtCall();
     }
@@ -38,6 +42,7 @@
       return "notInlinedPvtCallInnerInterface";
     }
 
+    @AlwaysInline
     private String nestPvtCallToInline() {
       return "nestPvtCallToInlineInnerInterface";
     }
@@ -84,22 +89,22 @@
     InnerInterface impl = new InnerInterfaceImpl();
 
     // Inlining through nest access (invoke virtual/interface).
-    System.out.println(i.nestPvtCallToInline());
-    System.out.println(impl.nestPvtCallToInline());
+    System.out.println((Object) i.nestPvtCallToInline());
+    System.out.println((Object) impl.nestPvtCallToInline());
 
     // Inlining transformations.
     // Invoke direct -> invoke virtual.
-    System.out.println(i.methodWithPvtCallToInline());
+    System.out.println((Object) i.methodWithPvtCallToInline());
     // Invoke interface -> invoke virtual.
-    System.out.println(impl.methodWithPvtCallToInline());
+    System.out.println((Object) impl.methodWithPvtCallToInline());
     // Invoke virtual -> invoke direct.
-    System.out.println(iSub.dispatchInlining(impl));
+    System.out.println((Object) iSub.dispatchInlining(impl));
     // Invoke interface -> invoke direct.
-    System.out.println(impl.dispatchInlining(iSub));
+    System.out.println((Object) impl.dispatchInlining(iSub));
 
     // Inheritance + invoke virtual and nest access.
     // This may mess up lookup logic.
-    System.out.println(iSub.nestPvtCallToInline());
-    System.out.println(((Inner) iSub).nestPvtCallToInline());
+    System.out.println((Object) iSub.nestPvtCallToInline());
+    System.out.println((Object) ((Inner) iSub).nestPvtCallToInline());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/B344363462Test.java b/src/test/java/com/android/tools/r8/B344363462Test.java
deleted file mode 100644
index bba41ed..0000000
--- a/src/test/java/com/android/tools/r8/B344363462Test.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.utils.StringUtils;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class B344363462Test extends TestBase {
-
-  @Parameter() public TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("-225");
-  private static final String UNEXPECTED_OUTPUT = StringUtils.lines("0");
-
-  @Test
-  public void testD8() throws Exception {
-    parameters.assumeDexRuntime();
-    testForD8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testD8Release() throws Exception {
-    parameters.assumeDexRuntime();
-    testForD8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .setMinApi(parameters)
-        .release()
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(UNEXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(UNEXPECTED_OUTPUT);
-  }
-
-  static class TestClass {
-
-    int iFld1;
-
-    void t() {
-      int i5 = 61127, i7 = 42011;
-      long[] lArr = new long[10];
-      try {
-        iFld1 -= 225L;
-        lArr[i5] = i7;
-        iFld1 = i7;
-      } catch (ArrayIndexOutOfBoundsException err) {
-      }
-      System.out.println(iFld1);
-    }
-
-    public static void main(String[] strArr) {
-      TestClass test = new TestClass();
-      test.t();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java
index a45b22e..e212a7a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionInstantiateAndCatchTest.java
@@ -160,12 +160,11 @@
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
-        .addDontObfuscate()
         .compile()
         .applyIf(
             exceptionPresentAtRuntime(), b -> b.addBootClasspathClasses(LibraryException.class))
         .run(parameters.getRuntime(), Main.class)
-        .apply(this::checkUnexpectedOutput)
+        .apply(this::checkOutput)
         .inspect(this::inspect);
   }
 
@@ -181,21 +180,6 @@
     }
   }
 
-  private void checkUnexpectedOutput(SingleTestRunResult<?> runResult) {
-    if (exceptionPresentAtRuntime()) {
-      if (parameters.isCfRuntime()
-          || parameters.getApiLevel().isGreaterThanOrEqualTo(mockExceptionLevel)) {
-        runResult.assertSuccessWithOutputLines("Valid behaviour");
-      } else {
-        // TODO(b/342961827): This should not happen.
-        runResult.assertSuccessWithOutputLines(
-            LibraryException.class.getTypeName() + ": Failed, true");
-      }
-    } else {
-      runResult.assertSuccessWithOutputLines("java.lang.NoClassDefFoundError, false");
-    }
-  }
-
   // Only present from api level P.
   public static class LibraryException extends Exception {
     public LibraryException(String message) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java
index a9ca217..6074c6e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfTryCatchReferenceTest.java
@@ -24,7 +24,7 @@
 @RunWith(Parameterized.class)
 public class ApiModelNoInliningOfTryCatchReferenceTest extends TestBase {
 
-  private final AndroidApiLevel exceptionApiLevel = AndroidApiLevel.L_MR1;
+  private final AndroidApiLevel exceptionApiLevel = AndroidApiLevel.M;
 
   @Parameter() public TestParameters parameters;
 
@@ -51,8 +51,9 @@
         .enableInliningAnnotations()
         .addHorizontallyMergedClassesInspector(
             horizontallyMergedClassesInspector -> {
+              // Dalvik verifier error present up to and not including L.
               if (parameters.isDexRuntime()
-                  && parameters.getApiLevel().isGreaterThanOrEqualTo(exceptionApiLevel)) {
+                  && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
                 horizontallyMergedClassesInspector.assertIsCompleteMergeGroup(
                     TestClass.class, Caller.class);
               } else {
@@ -63,11 +64,14 @@
             ApiModelingTestHelper.addTracedApiReferenceLevelCallBack(
                 (reference, apiLevel) -> {
                   if (reference.equals(Reference.methodFromMethod(tryCatch))) {
+                    // Dalvik verifier error present up to and not including L.
                     assertEquals(
-                        exceptionApiLevel.max(
-                            parameters.isCfRuntime()
-                                ? AndroidApiLevel.B
-                                : parameters.getApiLevel()),
+                        parameters.isDexRuntime()
+                                && parameters
+                                    .getApiLevel()
+                                    .isGreaterThanOrEqualTo(AndroidApiLevel.L)
+                            ? parameters.getApiLevel()
+                            : exceptionApiLevel,
                         apiLevel);
                   }
                 }))
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java
index 1cb50ba..917c268 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkBase.java
@@ -40,7 +40,7 @@
     config.run(new BenchmarkEnvironment(config, temp, false));
   }
 
-  public static BenchmarkRunner runner(BenchmarkConfig config) {
-    return BenchmarkRunner.runner(config);
+  public static BenchmarkRunner runner(BenchmarkEnvironment environment) {
+    return BenchmarkRunner.runner(environment);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java
index fe36c52..e8e5641 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java
@@ -19,6 +19,10 @@
     this.isGolem = isGolem;
   }
 
+  public boolean failOnCodeSizeDifferences() {
+    return System.getProperty("BENCHMARK_IGNORE_CODE_SIZE_DIFFERENCES") == null;
+  }
+
   public BenchmarkConfig getConfig() {
     return config;
   }
@@ -36,4 +40,20 @@
   public Path getGolemDependencyRoot() {
     return Paths.get("benchmarks", config.getDependencyDirectoryName());
   }
+
+  public boolean hasBenchmarkIterationsOverride() {
+    return System.getProperty("BENCHMARK_ITERATIONS") != null;
+  }
+
+  public int getBenchmarkIterationsOverride() {
+    return Integer.parseInt(System.getProperty("BENCHMARK_ITERATIONS"));
+  }
+
+  public boolean hasOutputPath() {
+    return System.getProperty("BENCHMARK_OUTPUT") != null;
+  }
+
+  public Path getOutputPath() {
+    return Paths.get(System.getProperty("BENCHMARK_OUTPUT"));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
index 14b10f4..da3f3d2 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.errors.Unimplemented;
+import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -46,12 +48,17 @@
   }
 
   @Override
-  public void printResults(ResultMode mode) {
+  public void printResults(ResultMode mode, boolean failOnCodeSizeDifferences) {
     List<String> sorted = new ArrayList<>(results.keySet());
     sorted.sort(String::compareTo);
     for (String name : sorted) {
       BenchmarkResultsSingle singleResults = results.get(name);
-      singleResults.printResults(mode);
+      singleResults.printResults(mode, failOnCodeSizeDifferences);
     }
   }
+
+  @Override
+  public void writeResults(PrintStream out) {
+    throw new Unimplemented();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
index e0bb9f193..e15dffb 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
+import java.io.PrintStream;
 import java.util.Set;
 
 public class BenchmarkResultsSingle implements BenchmarkResults {
@@ -19,6 +22,18 @@
     this.metrics = metrics;
   }
 
+  public String getName() {
+    return name;
+  }
+
+  public LongList getCodeSizeResults() {
+    return codeSizeResults;
+  }
+
+  public LongList getRuntimeResults() {
+    return runtimeResults;
+  }
+
   @Override
   public void addRuntimeResult(long result) {
     verifyMetric(BenchmarkMetric.RunTimeRaw, metrics.contains(BenchmarkMetric.RunTimeRaw), true);
@@ -75,7 +90,7 @@
   }
 
   @Override
-  public void printResults(ResultMode mode) {
+  public void printResults(ResultMode mode, boolean failOnCodeSizeDifferences) {
     verifyConfigAndResults();
     if (!runtimeResults.isEmpty()) {
       long sum = runtimeResults.stream().mapToLong(l -> l).sum();
@@ -84,13 +99,24 @@
     }
     if (!codeSizeResults.isEmpty()) {
       long size = codeSizeResults.getLong(0);
-      for (int i = 1; i < codeSizeResults.size(); i++) {
-        if (size != codeSizeResults.getLong(i)) {
-          throw new RuntimeException(
-              "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i));
+      if (failOnCodeSizeDifferences) {
+        for (int i = 1; i < codeSizeResults.size(); i++) {
+          if (size != codeSizeResults.getLong(i)) {
+            throw new RuntimeException(
+                "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i));
+          }
         }
       }
       printCodeSize(size);
     }
   }
+
+  @Override
+  public void writeResults(PrintStream out) {
+    Gson gson =
+        new GsonBuilder()
+            .registerTypeAdapter(BenchmarkResultsSingle.class, new BenchmarkResultsSingleAdapter())
+            .create();
+    out.print(gson.toJson(this));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
new file mode 100644
index 0000000..55936d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingleAdapter.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.benchmarks;
+
+import com.android.tools.r8.utils.ListUtils;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+
+public class BenchmarkResultsSingleAdapter implements JsonSerializer<BenchmarkResultsSingle> {
+
+  @Override
+  public JsonElement serialize(
+      BenchmarkResultsSingle result, Type type, JsonSerializationContext jsonSerializationContext) {
+    JsonArray resultsArray = new JsonArray();
+    ListUtils.forEachWithIndex(
+        result.getCodeSizeResults(),
+        (codeSizeResult, iteration) -> {
+          JsonObject resultObject = new JsonObject();
+          resultObject.addProperty("code_size", codeSizeResult);
+          resultObject.addProperty("runtime", result.getRuntimeResults().getLong(iteration));
+          resultsArray.add(resultObject);
+        });
+
+    JsonObject benchmarkObject = new JsonObject();
+    benchmarkObject.addProperty("benchmark_name", result.getName());
+    benchmarkObject.add("results", resultsArray);
+    return benchmarkObject;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
index 2ac97a6..1a4e33c 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.errors.Unimplemented;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
+import java.io.PrintStream;
 
 public class BenchmarkResultsWarmup implements BenchmarkResults {
 
@@ -51,7 +53,7 @@
   }
 
   @Override
-  public void printResults(ResultMode mode) {
+  public void printResults(ResultMode mode, boolean failOnCodeSizeDifferences) {
     if (runtimeResults.isEmpty()) {
       throw new BenchmarkConfigError("Expected runtime results for warmup run");
     }
@@ -61,4 +63,9 @@
         BenchmarkResults.prettyMetric(
             name, BenchmarkMetric.StartupTime, BenchmarkResults.prettyTime(result)));
   }
+
+  @Override
+  public void writeResults(PrintStream out) {
+    throw new Unimplemented();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
index 2fed6dc..78441e3 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
@@ -4,6 +4,9 @@
 package com.android.tools.r8.benchmarks;
 
 import com.android.tools.r8.benchmarks.BenchmarkResults.ResultMode;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
 
 public class BenchmarkRunner {
 
@@ -11,17 +14,17 @@
     void run(BenchmarkResults results) throws Exception;
   }
 
-  private final BenchmarkConfig config;
+  private final BenchmarkEnvironment environment;
   private int warmups = 0;
   private int iterations = 1;
   private ResultMode resultMode = BenchmarkResults.ResultMode.AVERAGE;
 
-  private BenchmarkRunner(BenchmarkConfig config) {
-    this.config = config;
+  private BenchmarkRunner(BenchmarkEnvironment environment) {
+    this.environment = environment;
   }
 
-  public static BenchmarkRunner runner(BenchmarkConfig config) {
-    return new BenchmarkRunner(config);
+  public static BenchmarkRunner runner(BenchmarkEnvironment environment) {
+    return new BenchmarkRunner(environment);
   }
 
   public BenchmarkRunner setWarmupIterations(int iterations) {
@@ -29,6 +32,12 @@
     return this;
   }
 
+  public int getBenchmarkIterations() {
+    return environment.hasBenchmarkIterationsOverride()
+        ? environment.getBenchmarkIterationsOverride()
+        : iterations;
+  }
+
   public BenchmarkRunner setBenchmarkIterations(int iterations) {
     this.iterations = iterations;
     return this;
@@ -46,6 +55,7 @@
 
   public void run(BenchmarkRunnerFunction fn) throws Exception {
     long warmupTotalTime = 0;
+    BenchmarkConfig config = environment.getConfig();
     BenchmarkResults warmupResults = new BenchmarkResultsWarmup(config.getName());
     if (warmups > 0) {
       long start = System.nanoTime();
@@ -59,7 +69,7 @@
             ? new BenchmarkResultsSingle(config.getName(), config.getMetrics())
             : new BenchmarkResultsCollection(config.getSubBenchmarks());
     long start = System.nanoTime();
-    for (int i = 0; i < iterations; i++) {
+    for (int i = 0; i < getBenchmarkIterations(); i++) {
       fn.run(results);
     }
     long benchmarkTotalTime = System.nanoTime() - start;
@@ -71,11 +81,14 @@
     if (warmups > 0) {
       printMetaInfo("warmup", warmups, warmupTotalTime);
       if (config.hasTimeWarmupRuns()) {
-        warmupResults.printResults(resultMode);
+        warmupResults.printResults(resultMode, environment.failOnCodeSizeDifferences());
       }
     }
-    printMetaInfo("benchmark", iterations, benchmarkTotalTime);
-    results.printResults(resultMode);
+    printMetaInfo("benchmark", getBenchmarkIterations(), benchmarkTotalTime);
+    results.printResults(resultMode, environment.failOnCodeSizeDifferences());
+    if (environment.hasOutputPath()) {
+      writeResults(results);
+    }
     System.out.println();
   }
 
@@ -84,4 +97,11 @@
     System.out.println("  " + kind + " iterations: " + iterations);
     System.out.println("  " + kind + " total time: " + BenchmarkResults.prettyTime(totalTime));
   }
+
+  private void writeResults(BenchmarkResults results) throws IOException {
+    try (PrintStream printStream =
+        new PrintStream(Files.newOutputStream(environment.getOutputPath()))) {
+      results.writeResults(printStream);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index 7e2a677..ac4f229 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -203,7 +203,7 @@
   private static BenchmarkMethod internalRunR8(
       AppDumpBenchmarkBuilder builder, boolean enableResourceShrinking) {
     return environment ->
-        BenchmarkBase.runner(environment.getConfig())
+        BenchmarkBase.runner(environment)
             .setWarmupIterations(1)
             .run(
                 results -> {
@@ -244,7 +244,7 @@
 
   private static BenchmarkMethod runBatchD8(AppDumpBenchmarkBuilder builder) {
     return environment ->
-        BenchmarkBase.runner(environment.getConfig())
+        BenchmarkBase.runner(environment)
             .setWarmupIterations(1)
             .run(
                 results -> {
@@ -262,7 +262,7 @@
 
   private static BenchmarkMethod runIncrementalD8(AppDumpBenchmarkBuilder builder) {
     return environment ->
-        BenchmarkBase.runner(environment.getConfig())
+        BenchmarkBase.runner(environment)
             .setWarmupIterations(1)
             .reportResultSum()
             .run(
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
index 3f12fc8..adfa5f8 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
@@ -65,7 +65,7 @@
             ImmutableSet.of(ANDROID_JAR.getRoot(environment).resolve("android.jar")),
             LibraryDesugaringSpecification.JDK11_DESCRIPTOR,
             "");
-    runner(environment.getConfig())
+    runner(environment)
         .setWarmupIterations(1)
         .setBenchmarkIterations(10)
         .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java
index 6b5e4f9..2d50a3d 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/LegacyDesugaredLibraryBenchmark.java
@@ -54,7 +54,7 @@
   }
 
   public static void run(BenchmarkEnvironment environment) throws Exception {
-    runner(environment.getConfig())
+    runner(environment)
         .setWarmupIterations(1)
         .setBenchmarkIterations(10)
         .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
index cb43a1d..4c8c31c 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/helloworld/HelloWorldBenchmark.java
@@ -101,7 +101,7 @@
 
   public static BenchmarkMethod benchmarkD8(Options options) {
     return environment ->
-        runner(environment.getConfig())
+        runner(environment)
             .setWarmupIterations(1)
             .setBenchmarkIterations(100)
             .reportResultSum()
@@ -119,7 +119,7 @@
 
   public static BenchmarkMethod benchmarkR8(Options options) {
     return environment ->
-        runner(environment.getConfig())
+        runner(environment)
             .setWarmupIterations(1)
             .setBenchmarkIterations(4)
             .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
index 28e1e83..c3cd9d0 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
@@ -59,7 +59,7 @@
 
   public static BenchmarkMethod benchmarkRetrace() {
     return environment ->
-        runner(environment.getConfig())
+        runner(environment)
             .setWarmupIterations(1)
             .setBenchmarkIterations(4)
             .reportResultSum()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
index fa46f4d..0d912b3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -36,9 +35,7 @@
         .assertSuccessWithOutputLines("foo", "hello", "5", "foobar")
         .inspect(
             codeInspector -> {
-              assertThat(
-                  codeInspector.clazz(C.class),
-                  isAbsentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
+              assertThat(codeInspector.clazz(C.class), isPresent());
               assertThat(codeInspector.clazz(D.class), isPresent());
               assertThat(codeInspector.clazz(E.class), isPresent());
             });
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java
index 8f73003..cba6309 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAccessControlTestUtils.java
@@ -4,13 +4,9 @@
 
 package com.android.tools.r8.desugar.nestaccesscontrol;
 
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
 
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.nio.file.Path;
@@ -23,7 +19,6 @@
       Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR).resolve("nesthostexample" + JAR_EXTENSION);
   public static final Path CLASSES_PATH =
       Paths.get(ToolHelper.getExamplesJava11BuildDir()).resolve("nesthostexample/");
-  public static final String PACKAGE_NAME = "nesthostexample.";
 
   public static final List<String> CLASS_NAMES =
       ImmutableList.of(
@@ -68,13 +63,6 @@
           "NestHostExample$StaticNestMemberInner$StaticNestMemberInnerInner",
           "NestHostExample$StaticNestInterfaceInner",
           "NestHostExample$ExampleEnumCompilation");
-  public static final int NUMBER_OF_TEST_CLASSES = CLASS_NAMES.size();
-
-  // The following map use ids, i.e., strings which represents respectively
-  // a nest with only field, method, constructor, anonymous class and
-  // all at once nest based private accesses.
-  public static final ImmutableList<String> NEST_IDS =
-      ImmutableList.of("fields", "methods", "constructors", "anonymous", "all");
   public static final ImmutableMap<String, String> MAIN_CLASSES =
       ImmutableMap.<String, String>builder()
           .put("fields", "BasicNestHostWithInnerClassFields")
@@ -89,94 +77,10 @@
           .put("pvtCallInlined", "NestPvtMethodCallInlined")
           .put("memberPropagated", "NestPvtFieldPropagated")
           .build();
-  public static final String ALL_RESULT_LINE =
-      String.join(
-          ", ",
-          new String[] {
-            "field",
-            "staticField",
-            "staticField",
-            "hostMethod",
-            "staticHostMethod",
-            "staticHostMethod",
-            "nest1SField",
-            "staticNest1SField",
-            "staticNest1SField",
-            "nest1SMethod",
-            "staticNest1SMethod",
-            "staticNest1SMethod",
-            "nest2SField",
-            "staticNest2SField",
-            "staticNest2SField",
-            "nest2SMethod",
-            "staticNest2SMethod",
-            "staticNest2SMethod",
-            "nest1Field",
-            "nest1Method",
-            "nest2Field",
-            "nest2Method"
-          });
-  public static final ImmutableMap<String, String> EXPECTED_RESULTS =
-      ImmutableMap.<String, String>builder()
-          .put(
-              "fields",
-              StringUtils.lines(
-                  "RWnestFieldRWRWnestFieldRWRWnestFieldnoBridge", "RWfieldRWRWfieldRWRWnestField"))
-          .put(
-              "methods",
-              StringUtils.lines(
-                  "nestMethodstaticNestMethodstaticNestMethodnoBridge",
-                  "hostMethodstaticHostMethodstaticNestMethod"))
-          .put(
-              "constructors",
-              StringUtils.lines(
-                  "field", "nest1SField", "1", "innerFieldUnusedConstructor", "nothing"))
-          .put(
-              "anonymous",
-              StringUtils.lines(
-                  "fieldstaticFieldstaticFieldhostMethodstaticHostMethodstaticHostMethod"))
-          .put(
-              "all",
-              StringUtils.lines(
-                  ALL_RESULT_LINE,
-                  ALL_RESULT_LINE,
-                  ALL_RESULT_LINE,
-                  ALL_RESULT_LINE,
-                  "staticInterfaceMethodstaticStaticInterfaceMethod",
-                  "staticInterfaceMethodstaticStaticInterfaceMethod",
-                  "staticInterfaceMethodstaticStaticInterfaceMethod",
-                  "staticInterfaceMethodstaticStaticInterfaceMethod",
-                  "3"))
-          .put(
-              "pvtCallInlined",
-              StringUtils.lines(
-                  "nestPvtCallToInlineInner",
-                  "nestPvtCallToInlineInnerInterface",
-                  "notInlinedPvtCallInner",
-                  "notInlinedPvtCallInnerInterface",
-                  "notInlinedPvtCallInnerSub",
-                  "notInlinedPvtCallInnerInterface",
-                  "nestPvtCallToInlineInnerSub",
-                  "nestPvtCallToInlineInner"))
-          .put("memberPropagated", StringUtils.lines("toPropagateStatic"))
-          .build();
 
   public static String getMainClass(String id) {
-    return PACKAGE_NAME + MAIN_CLASSES.get(id);
+    return "nesthostexample." + MAIN_CLASSES.get(id);
   }
 
-  public static String getExpectedResult(String id) {
-    return EXPECTED_RESULTS.get(id);
-  }
 
-  public static List<Path> classesOfNest(String nestID) {
-    return classesMatching(MAIN_CLASSES.get(nestID));
-  }
-
-  public static List<Path> classesMatching(String matcher) {
-    return CLASS_NAMES.stream()
-        .filter(name -> containsString(matcher).matches(name))
-        .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
-        .collect(toList());
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
deleted file mode 100644
index 464e2c1..0000000
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.nestaccesscontrol;
-
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.JAR;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getExpectedResult;
-import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.getMainClass;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-import static java.util.stream.Collectors.toList;
-import static org.hamcrest.core.StringContains.containsString;
-import static org.hamcrest.core.StringEndsWith.endsWith;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import java.nio.file.Path;
-import java.util.List;
-import org.hamcrest.Matcher;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class NestOnProgramAndClassPathTest extends TestBase {
-
-  public NestOnProgramAndClassPathTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
-  }
-
-  @Test
-  public void testD8MethodBridgesPresent() throws Exception {
-    parameters.assumeDexRuntime();
-    // 1 inner class.
-    D8TestCompileResult singleInner =
-        compileClassesWithD8ProgramClassesMatching(
-            containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass"));
-    singleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 2));
-    // Outer class.
-    D8TestCompileResult host =
-        compileClassesWithD8ProgramClassesMatching(endsWith("BasicNestHostWithInnerClassMethods"));
-    host.inspect(inspector -> assertThisNumberOfBridges(inspector, 2));
-    // 2 inner classes.
-    D8TestCompileResult multipleInner =
-        compileClassesWithD8ProgramClassesMatching(
-            containsString("NestHostExample$StaticNestMemberInner"));
-    multipleInner.inspect(inspector -> assertThisNumberOfBridges(inspector, 5));
-  }
-
-  @Test
-  public void testD8ConstructorBridgesPresent() throws Exception {
-    parameters.assumeDexRuntime();
-    D8TestCompileResult inner =
-        compileClassesWithD8ProgramClassesMatching(
-            containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass"));
-    inner.inspect(
-        inspector -> {
-          assertThisNumberOfBridges(inspector, 3);
-          assertNestConstructor(inspector);
-        });
-    D8TestCompileResult host =
-        compileClassesWithD8ProgramClassesMatching(
-            endsWith("BasicNestHostWithInnerClassConstructors"));
-    host.inspect(
-        inspector -> {
-          assertThisNumberOfBridges(inspector, 1);
-          assertNestConstructor(inspector);
-        });
-  }
-
-  @Test
-  public void testD8ConstructorNestMergeCorrect() throws Exception {
-    // Multiple Nest Constructor classes have to be merged here.
-    parameters.assumeDexRuntime();
-    D8TestCompileResult inner =
-        compileClassesWithD8ProgramClassesMatching(
-            containsString("BasicNestHostWithInnerClassConstructors$BasicNestedClass"));
-    D8TestCompileResult host =
-        compileClassesWithD8ProgramClassesMatching(
-            endsWith("BasicNestHostWithInnerClassConstructors"));
-    testForD8()
-        .addProgramFiles(inner.writeToZip(), host.writeToZip())
-        .setMinApi(parameters)
-        .compile()
-        .run(parameters.getRuntime(), getMainClass("constructors"))
-        .assertSuccessWithOutput(getExpectedResult("constructors"));
-  }
-
-  private D8TestCompileResult compileClassesWithD8ProgramClassesMatching(Matcher<String> matcher)
-      throws Exception {
-    List<Path> matchingClasses =
-        CLASS_NAMES.stream()
-            .filter(matcher::matches)
-            .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
-            .collect(toList());
-    return testForD8()
-        .setMinApi(parameters)
-        .addProgramFiles(matchingClasses)
-        .addClasspathFiles(JAR)
-        .compile();
-  }
-
-  private static void assertNestConstructor(CodeInspector inspector) {
-    assertTrue(inspector.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic));
-  }
-
-  private static void assertThisNumberOfBridges(CodeInspector inspector, int numBridges) {
-    for (FoundClassSubject clazz : inspector.allClasses()) {
-      if (!clazz.isSynthetic()) {
-        assertEquals(numBridges, clazz.allMethods(FoundMethodSubject::isSynthetic).size());
-      }
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index c7c00b8..6499f3d 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.callgraph.Node;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.ReachabilitySensitiveValue;
 import java.util.Collections;
 
 class CallGraphTestBase extends TestBase {
@@ -44,7 +45,8 @@
           DexEncodedField.EMPTY_ARRAY,
           MethodCollectionFactory.empty(),
           false,
-          DexProgramClass::invalidChecksumRequest);
+          DexProgramClass::invalidChecksumRequest,
+          ReachabilitySensitiveValue.DISABLED);
 
   Node createNode(String methodName) {
     DexMethod signature =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270ConstClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270ConstClassTest.java
new file mode 100644
index 0000000..684f3a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270ConstClassTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270.I;
+import com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270.PublicAccessor;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class Regress345248270ConstClassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("true");
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(
+            I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(
+            I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .enableNoAccessModificationAnnotationsForMembers()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class TestClass {
+    public static Class<?> test() {
+      Class<?> r = null;
+      if (System.currentTimeMillis() > 0) {
+        r = PublicAccessor.getPackagePrivateImplementationClass();
+      } else {
+        System.out.println("Do something");
+        r = PublicAccessor.getPackagePrivateImplementationClass();
+      }
+      return r;
+    }
+
+    public static Class<?> test2() {
+      return System.currentTimeMillis() > 0
+          ? PublicAccessor.getPackagePrivateImplementationClass()
+          : null;
+    }
+
+    public static void main(String[] args) {
+      Class<?> c = test();
+      System.out.println(c == test2());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270Test.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270Test.java
new file mode 100644
index 0000000..0b0fcb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/Regress345248270Test.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270.I;
+import com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270.PublicAccessor;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+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 Regress345248270Test extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("true");
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(
+            I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(
+            I.class, PublicAccessor.class, PublicAccessor.getPackagePrivateImplementationClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .enableNoAccessModificationAnnotationsForMembers()
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(TestClass.class), isPresent());
+              // test is inlined into main.
+              assertThat(
+                  inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("test"),
+                  Matchers.isAbsent());
+              ClassSubject packagePrivateImplementation =
+                  inspector.clazz(PublicAccessor.getPackagePrivateImplementationClass());
+              assertThat(packagePrivateImplementation, isPresent());
+              // No direct field access of the package private field.
+              assertTrue(
+                  inspector
+                      .clazz(TestClass.class)
+                      .mainMethod()
+                      .streamInstructions()
+                      .filter(InstructionSubject::isFieldAccess)
+                      .map(InstructionSubject::getField)
+                      .noneMatch(
+                          f ->
+                              f.getType()
+                                  .isIdenticalTo(
+                                      packagePrivateImplementation
+                                          .getDexProgramClass()
+                                          .getType())));
+            })
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class TestClass {
+    public static I test() {
+      I r = null;
+      if (System.currentTimeMillis() > 0) {
+        r = PublicAccessor.getPackagePrivateImplementation();
+      } else {
+        System.out.println("Do something");
+        r = PublicAccessor.getPackagePrivateImplementation();
+      }
+      return r;
+    }
+
+    public static I test2() {
+      return System.currentTimeMillis() > 0
+          ? PublicAccessor.getPackagePrivateImplementation()
+          : null;
+    }
+
+    public static void main(String[] args) {
+      I i = test();
+      System.out.println(i == test2());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/I.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/I.java
new file mode 100644
index 0000000..ea07f32
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/I.java
@@ -0,0 +1,6 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270;
+
+public interface I {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PackagePrivateImplementation.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PackagePrivateImplementation.java
new file mode 100644
index 0000000..7d615db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PackagePrivateImplementation.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoAccessModification;
+
+@NeverClassInline
+class PackagePrivateImplementation implements I {
+  @NoAccessModification static final I NULL = new PackagePrivateImplementation();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PublicAccessor.java b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PublicAccessor.java
new file mode 100644
index 0000000..52ab243
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/effectivelytrivialphioptimization/b345248270/PublicAccessor.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.effectivelytrivialphioptimization.b345248270;
+
+public class PublicAccessor {
+  public static I getPackagePrivateImplementation() {
+    return PackagePrivateImplementation.NULL;
+  }
+
+  public static Class<?> getPackagePrivateImplementationClass() {
+    return PackagePrivateImplementation.class;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
index ea19697..4ff3f4f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.inliner;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
@@ -87,10 +87,16 @@
         .transform();
   }
 
-  private boolean compilationTargetIsMissingExceptionType() {
+  private boolean isStubbed() {
+    // Note: this is a simplified version of ApiReferenceStubber.isAlwaysStubbedType only testing
+    // the types relevant for this test.
+    return !exception.startsWith("java.") && !exception.startsWith("javax.");
+  }
+
+  private boolean isPresentInRuntime() {
     // A CF target could target any API in the end.
-    return parameters.isCfRuntime()
-        || parameters.getApiLevel().getLevel() < EXCEPTIONS.get(exception);
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().getLevel() >= EXCEPTIONS.get(exception);
   }
 
   @Test
@@ -101,7 +107,7 @@
         .addProgramClassFileData(getClassWithCatchHandler())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters)
-        // Use the latest library so that all of the exceptions are defined.
+        // Use the latest library so that all the exceptions are defined.
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
         .compile()
         .inspect(this::checkInlined)
@@ -111,7 +117,7 @@
 
   private void checkResult(R8TestRunResult runResult) {
     // The bootclasspath for our build of 4.4.4 does not contain various bits. Allow verify error.
-    if (!compilationTargetIsMissingExceptionType()
+    if (isPresentInRuntime()
         && parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V4_4_4)
         && (exception.startsWith("android.media") || exception.startsWith("android.view"))) {
       runResult.assertFailureWithErrorThatThrows(VerifyError.class);
@@ -126,10 +132,15 @@
     boolean mainHasInlinedCatchHandler =
         Streams.stream(classSubject.mainMethod().iterateTryCatches())
             .anyMatch(tryCatch -> tryCatch.isCatching(exception));
-    if (compilationTargetIsMissingExceptionType()) {
+    if (parameters.isCfRuntime()) {
       assertFalse(mainHasInlinedCatchHandler);
     } else {
-      assertTrue(mainHasInlinedCatchHandler);
+      assertEquals(
+          // Dalvik verifier error present up to and not including L.
+          parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.L)
+              ? parameters.getApiLevel().getLevel() >= EXCEPTIONS.get(exception)
+              : isPresentInRuntime() || isStubbed(),
+          mainHasInlinedCatchHandler);
     }
   }
 
@@ -137,7 +148,7 @@
 
     public static void main(String[] args) {
       if (args.length == 200) {
-        // Never called
+        // Never called.
         ClassWithCatchHandler.methodWithCatch();
       }
       System.out.println("Done...");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java
index 142d565..f952fa9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldWithConstructorInliningTest.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
@@ -42,11 +42,17 @@
         .inspect(
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isAbsent());
+              // TODO(b/339210038): Should always be absent.
+              assertThat(
+                  aClassSubject,
+                  isPresentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
 
               MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
               assertThat(mainMethodSubject, isPresent());
-              assertTrue(mainMethodSubject.streamInstructions().anyMatch(i -> i.isConstNumber(42)));
+              // TODO(b/339210038): Should always contain 42.
+              assertEquals(
+                  parameters.canInitNewInstanceUsingSuperclassConstructor(),
+                  mainMethodSubject.streamInstructions().noneMatch(i -> i.isConstNumber(42)));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("42");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
index 862fbc9..fca4473 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantArgumentMultipleConstructorsTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -18,20 +18,21 @@
 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 FieldInitializedByConstantArgumentMultipleConstructorsTest extends TestBase {
 
-  @Parameter(0)
-  public TestParameters parameters;
+  private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
+  @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
+  public FieldInitializedByConstantArgumentMultipleConstructorsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -50,7 +51,10 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithOriginalName("live"), isPresent());
-    assertThat(testClassSubject.uniqueMethodWithOriginalName("dead"), isAbsent());
+    // TODO(b/280275115): Constructor inlining regresses instance field value analysis.
+    assertThat(
+        testClassSubject.uniqueMethodWithOriginalName("dead"),
+        isPresentIf(parameters.canInitNewInstanceUsingSuperclassConstructor()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java
new file mode 100644
index 0000000..de5ba07
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java
@@ -0,0 +1,278 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.redundantarraygetelimination;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantArrayGetEliminationWithThrowingArrayAccessTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("-1", "-3", "-6", "-10", "42011", "9", "42010", "8", "13");
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void countInstanceGetOfField(int count, MethodSubject method, FieldSubject field) {
+    assertThat(method, isPresentAndRenamed());
+    assertThat(field, isPresentAndRenamed());
+    assertEquals(
+        count,
+        method
+            .streamInstructions()
+            .filter(InstructionSubject::isInstancePut)
+            .map(InstructionSubject::getField)
+            .filter(f -> f.isIdenticalTo(field.getDexField()))
+            .count());
+  }
+
+  private void zeroArrayGet(MethodSubject method) {
+    countArrayGet(0, method);
+  }
+
+  private void oneArrayGet(MethodSubject method) {
+    countArrayGet(1, method);
+  }
+
+  private void countArrayGet(int count, MethodSubject method) {
+    assertThat(method, isPresentAndRenamed());
+    assertEquals(count, method.streamInstructions().filter(InstructionSubject::isArrayGet).count());
+  }
+
+  private void oneInstanceGetOfField(MethodSubject method, FieldSubject field) {
+    countInstanceGetOfField(1, method, field);
+  }
+
+  private void twoInstanceGetOfField(MethodSubject method, FieldSubject field) {
+    countInstanceGetOfField(2, method, field);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClass = inspector.clazz(TestClass.class);
+    assertThat(testClass, isPresentAndNotRenamed());
+    FieldSubject iFld1 = testClass.uniqueFieldWithOriginalName("iFld1");
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutThrows"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutThrows"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetThrows"), iFld1);
+    oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetThrows"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutThrows"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutThrows"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetThrows"), iFld1);
+    oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetThrows"));
+    oneInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutSucceeds"));
+    oneInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetSucceeds"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutSucceeds"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetSucceeds"), iFld1);
+    oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetSucceeds"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutAndGetSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutAndGetSucceeds"));
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .release()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters)
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        .inspect(this::inspect);
+  }
+
+  static class TestClass {
+
+    int iFld1;
+
+    long[] array() {
+      return new long[System.currentTimeMillis() > 0 ? 10 : 20];
+    }
+
+    @NeverInline
+    void arrayOfKnownSizePutThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 1L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfKnownSizeGetThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 2L;
+        long x = lArr[i5];
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizePutThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = array();
+      try {
+        iFld1 -= 3L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizeGetThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = array();
+      try {
+        iFld1 -= 4L;
+        long x = lArr[i5];
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfKnownSizePutSucceeds() {
+      int i5 = 9, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 5L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfKnownSizeGetSucceeds() {
+      int i5 = 9, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 6L;
+        long x = lArr[i5];
+        iFld1 = i5;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizePutSucceeds() {
+      int i5 = 9, i7 = 42010;
+      long[] lArr = array();
+      try {
+        iFld1 -= 225L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizeGetSucceeds() {
+      int i5 = 8, i7 = 42011;
+      long[] lArr = array();
+      try {
+        iFld1 -= 225L;
+        long x = lArr[i5];
+        iFld1 = i5;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizePutAndGetSucceeds() {
+      int i5 = 0, i7 = 13;
+      long[] lArr = array();
+      try {
+        iFld1 -= 5L;
+        lArr[i5] = i7;
+        iFld1 = (int) lArr[i5];
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    public static void main(String[] strArr) {
+      TestClass test = new TestClass();
+      test.arrayOfKnownSizePutThrows();
+      test.arrayOfKnownSizeGetThrows();
+      test.arrayOfUnknownSizePutThrows();
+      test.arrayOfUnknownSizeGetThrows();
+      test.arrayOfKnownSizePutSucceeds();
+      test.arrayOfKnownSizeGetSucceeds();
+      test.arrayOfUnknownSizePutSucceeds();
+      test.arrayOfUnknownSizeGetSucceeds();
+      test.arrayOfUnknownSizePutAndGetSucceeds();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java
index 02195c5..43cf4a7 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ClassNameStringPatternsTest.java
@@ -80,7 +80,7 @@
             @TypePattern(
                 classNamePattern =
                     @ClassNamePattern(
-                        simpleNamePattern =
+                        unqualifiedNamePattern =
                             @StringPattern(startsWith = "ClassNameStringPatternsTest$Foo")))
           }),
       @KeepTarget(
@@ -89,7 +89,7 @@
           methodParameterTypePatterns = {
             @TypePattern(
                 classNamePattern =
-                    @ClassNamePattern(simpleNamePattern = @StringPattern(endsWith = "Bar")))
+                    @ClassNamePattern(unqualifiedNamePattern = @StringPattern(endsWith = "Bar")))
           }),
     })
     public void foo() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
index 52850da..a024909 100644
--- a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
@@ -121,10 +121,11 @@
           classConstant = OnMethods.class,
           kind = KeepItemKind.CLASS_AND_METHODS,
           methodAnnotatedByClassNamePattern =
-              @ClassNamePattern(simpleName = "MembersAnnotatedByPatternsTest$C"),
+              @ClassNamePattern(unqualifiedName = "MembersAnnotatedByPatternsTest$C"),
           constrainAnnotations =
               @AnnotationPattern(
-                  namePattern = @ClassNamePattern(simpleName = "MembersAnnotatedByPatternsTest$C")))
+                  namePattern =
+                      @ClassNamePattern(unqualifiedName = "MembersAnnotatedByPatternsTest$C")))
     })
     public void foo(Class<?> clazz) throws Exception {
       for (Field field : clazz.getDeclaredFields()) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
index 699ddd6..d6b97d1 100644
--- a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
@@ -195,7 +195,7 @@
     @UsesReflection({
       @KeepTarget(
           kind = KeepItemKind.CLASS_AND_METHODS,
-          classNamePattern = @ClassNamePattern(simpleName = "B"),
+          classNamePattern = @ClassNamePattern(unqualifiedName = "B"),
           methodName = "foo",
           methodReturnTypeConstant = String.class)
     })
@@ -216,13 +216,13 @@
             kind = KeepItemKind.CLASS_AND_METHODS,
             classNamePattern =
                 @ClassNamePattern(
-                    simpleName = "A",
+                    unqualifiedName = "A",
                     packageName = "com.android.tools.r8.keepanno.classpatterns.pkg2"),
             methodName = "foo",
             methodReturnTypePattern =
                 @TypePattern(
                     classNamePattern =
-                        @ClassNamePattern(packageName = "java.lang", simpleName = "String"))))
+                        @ClassNamePattern(packageName = "java.lang", unqualifiedName = "String"))))
     public void foo() throws Exception {
       Util.lookupClassesAndInvokeMethods();
     }
@@ -240,11 +240,11 @@
             kind = KeepItemKind.CLASS_AND_METHODS,
             classNamePattern =
                 @ClassNamePattern(
-                    simpleName = "A",
+                    unqualifiedName = "A",
                     packageName = "com.android.tools.r8.keepanno.classpatterns.pkg2"),
             methodName = "foo",
             methodReturnTypePattern =
-                @TypePattern(classNamePattern = @ClassNamePattern(simpleName = "String"))))
+                @TypePattern(classNamePattern = @ClassNamePattern(unqualifiedName = "String"))))
     public void foo() throws Exception {
       Util.lookupClassesAndInvokeMethods();
     }
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
index f79c36e..5a45f17 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
@@ -25,8 +25,8 @@
 import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.USED_BY_NATIVE;
 import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.USED_BY_REFLECTION;
 import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.USES_REFLECTION;
+import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.getUnqualifiedName;
 import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.quote;
-import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.simpleName;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.keepanno.doctests.ForApiDocumentationTest;
@@ -116,7 +116,7 @@
   }
 
   private static String getPrefix(ClassReference annoType) {
-    return "`@" + simpleName(annoType);
+    return "`@" + getUnqualifiedName(annoType);
   }
 
   private static String getSuffix() {
@@ -196,24 +196,24 @@
   }
 
   private String getMdAnnotationLink(ClassReference clazz) {
-    return "[@" + simpleName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")";
+    return "[@" + getUnqualifiedName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")";
   }
 
   private String getMdAnnotationPropertyLink(ClassReference clazz, GroupMember method) {
     String methodName = method.name;
     String url = getClassJavaDocUrl(clazz) + "#" + methodName + "()";
-    return "[@" + simpleName(clazz) + "." + methodName + "](" + url + ")";
+    return "[@" + getUnqualifiedName(clazz) + "." + methodName + "](" + url + ")";
   }
 
   private String getMdEnumLink(ClassReference clazz) {
-    return "[" + simpleName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")";
+    return "[" + getUnqualifiedName(clazz) + "](" + getClassJavaDocUrl(clazz) + ")";
   }
 
   private String getMdEnumMemberLink(EnumReference enumMember) {
     ClassReference clazz = enumMember.enumClass;
     String enumName = enumMember.name();
     String url = getClassJavaDocUrl(clazz) + "#" + enumName;
-    return "[" + simpleName(clazz) + "." + enumName + "](" + url + ")";
+    return "[" + getUnqualifiedName(clazz) + "." + enumName + "](" + url + ")";
   }
 
   private void println() {
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index c2a658d..b6be4dc 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -199,20 +199,20 @@
       ImmutableList.of(FIELD_ACCESS_VOLATILE, FIELD_ACCESS_TRANSIENT);
 
   private static final String DEFAULT_INVALID_STRING_PATTERN =
-      "@" + simpleName(STRING_PATTERN) + "(exact = \"\")";
+      "@" + getUnqualifiedName(STRING_PATTERN) + "(exact = \"\")";
   private static final String DEFAULT_INVALID_TYPE_PATTERN =
-      "@" + simpleName(TYPE_PATTERN) + "(name = \"\")";
+      "@" + getUnqualifiedName(TYPE_PATTERN) + "(name = \"\")";
   private static final String DEFAULT_INVALID_CLASS_NAME_PATTERN =
-      "@" + simpleName(CLASS_NAME_PATTERN) + "(simpleName = \"\")";
+      "@" + getUnqualifiedName(CLASS_NAME_PATTERN) + "(unqualifiedName = \"\")";
   private static final String DEFAULT_ANY_INSTANCE_OF_PATTERN =
-      "@" + simpleName(INSTANCE_OF_PATTERN) + "()";
+      "@" + getUnqualifiedName(INSTANCE_OF_PATTERN) + "()";
 
-  private static ClassReference astClass(String simpleName) {
-    return classFromTypeName(AST_PKG + simpleName);
+  private static ClassReference astClass(String unqualifiedName) {
+    return classFromTypeName(AST_PKG + unqualifiedName);
   }
 
-  private static ClassReference annoClass(String simpleName) {
-    return classFromTypeName(ANNO_PKG + simpleName);
+  private static ClassReference annoClass(String unqualifiedName) {
+    return classFromTypeName(ANNO_PKG + unqualifiedName);
   }
 
   private static EnumReference enumRef(ClassReference enumClass, String valueName) {
@@ -223,7 +223,7 @@
     return "\"" + str + "\"";
   }
 
-  public static String simpleName(ClassReference clazz) {
+  public static String getUnqualifiedName(ClassReference clazz) {
     String binaryName = clazz.getBinaryName();
     return binaryName.substring(binaryName.lastIndexOf('/') + 1);
   }
@@ -271,12 +271,12 @@
 
     public GroupMember requiredValue(ClassReference type) {
       assert valueDefault == null;
-      return setType(simpleName(type));
+      return setType(getUnqualifiedName(type));
     }
 
     public GroupMember requiredArrayValue(ClassReference type) {
       assert valueDefault == null;
-      return setType(simpleName(type) + "[]");
+      return setType(getUnqualifiedName(type) + "[]");
     }
 
     public GroupMember requiredStringValue() {
@@ -289,12 +289,12 @@
     }
 
     public GroupMember defaultValue(ClassReference type, String value) {
-      setType(simpleName(type));
+      setType(getUnqualifiedName(type));
       return setValue(value);
     }
 
     public GroupMember defaultArrayValue(ClassReference type, String value) {
-      setType(simpleName(type) + "[]");
+      setType(getUnqualifiedName(type) + "[]");
       return setValue("{" + value + "}");
     }
 
@@ -640,21 +640,23 @@
                   .defaultObjectClass());
     }
 
-    private Group classNamePatternSimpleNameGroup() {
-      return new Group("class-simple-name")
+    private Group classNamePatternUnqualifiedNameGroup() {
+      return new Group("class-unqualified-name")
           .addMember(
-              new GroupMember("simpleName")
-                  .setDocTitle("Exact simple name of the class or interface.")
+              new GroupMember("unqualifiedName")
+                  .setDocTitle("Exact and unqualified name of the class or interface.")
                   .addParagraph(
-                      "For example, the simple name of {@code com.example.MyClass} is {@code"
-                          + " MyClass}.")
-                  .addParagraph("The default matches any simple name.")
+                      "For example, the unqualified name of {@code com.example.MyClass} is {@code"
+                          + " MyClass}.",
+                      "Note that for inner classes a `$` will appear in the unqualified name,"
+                          + "such as, {@code MyClass$MyInnerClass}.")
+                  .addParagraph("The default matches any unqualified name.")
                   .defaultEmptyString())
           .addMember(
-              new GroupMember("simpleNamePattern")
-                  .setDocTitle("Define the simple-name pattern by a string pattern.")
-                  .addParagraph("The default matches any simple name.")
-                  .setDocReturn("The string pattern of the class simple name.")
+              new GroupMember("unqualifiedNamePattern")
+                  .setDocTitle("Define the unqualified class-name pattern by a string pattern.")
+                  .setDocReturn("The string pattern of the unqualified class name.")
+                  .addParagraph("The default matches any unqualified name.")
                   .defaultValue(STRING_PATTERN, DEFAULT_INVALID_STRING_PATTERN));
     }
 
@@ -1246,7 +1248,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(STRING_PATTERN) + " {");
+      println("public @interface " + getUnqualifiedName(STRING_PATTERN) + " {");
       println();
       withIndent(
           () -> {
@@ -1275,7 +1277,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(TYPE_PATTERN) + " {");
+      println("public @interface " + getUnqualifiedName(TYPE_PATTERN) + " {");
       println();
       withIndent(() -> typePatternGroup().generate(this));
       println();
@@ -1294,18 +1296,18 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(CLASS_NAME_PATTERN) + " {");
+      println("public @interface " + getUnqualifiedName(CLASS_NAME_PATTERN) + " {");
       println();
       withIndent(
           () -> {
             Group exactNameGroup = classNamePatternFullNameGroup();
-            Group simpleNameGroup = classNamePatternSimpleNameGroup();
+            Group unqualifiedNameGroup = classNamePatternUnqualifiedNameGroup();
             Group packageGroup = classNamePatternPackageGroup();
-            exactNameGroup.addMutuallyExclusiveGroups(simpleNameGroup, packageGroup);
+            exactNameGroup.addMutuallyExclusiveGroups(unqualifiedNameGroup, packageGroup);
 
             exactNameGroup.generate(this);
             println();
-            simpleNameGroup.generate(this);
+            unqualifiedNameGroup.generate(this);
             println();
             packageGroup.generate(this);
           });
@@ -1323,7 +1325,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(INSTANCE_OF_PATTERN) + " {");
+      println("public @interface " + getUnqualifiedName(INSTANCE_OF_PATTERN) + " {");
       println();
       withIndent(
           () -> {
@@ -1347,7 +1349,7 @@
           .printDoc(this::println);
       println("@Target(ElementType.ANNOTATION_TYPE)");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(ANNOTATION_PATTERN) + " {");
+      println("public @interface " + getUnqualifiedName(ANNOTATION_PATTERN) + " {");
       println();
       withIndent(
           () -> {
@@ -1522,10 +1524,10 @@
               "Assume the item of the annotation is denoted by 'CTX' and referred to as its"
                   + " context.")
           .addCodeBlock(
-              annoSimpleName(USES_REFLECTION)
+              annoUnqualifiedName(USES_REFLECTION)
                   + "(value = targets, [additionalPreconditions = preconditions])",
               "==>",
-              annoSimpleName(KEEP_EDGE) + "(",
+              annoUnqualifiedName(KEEP_EDGE) + "(",
               "  consequences = targets,",
               "  preconditions = {createConditionFromContext(CTX)} + preconditions",
               ")",
@@ -1555,7 +1557,7 @@
           "@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,"
               + " ElementType.CONSTRUCTOR})");
       println("@Retention(RetentionPolicy.CLASS)");
-      println("public @interface " + simpleName(USES_REFLECTION) + " {");
+      println("public @interface " + getUnqualifiedName(USES_REFLECTION) + " {");
       println();
       withIndent(
           () -> {
@@ -1642,12 +1644,12 @@
       println("}");
     }
 
-    private static String annoSimpleName(ClassReference clazz) {
-      return "@" + simpleName(clazz);
+    private static String annoUnqualifiedName(ClassReference clazz) {
+      return "@" + getUnqualifiedName(clazz);
     }
 
     private static String docLink(ClassReference clazz) {
-      return "{@link " + simpleName(clazz) + "}";
+      return "{@link " + getUnqualifiedName(clazz) + "}";
     }
 
     private static String docLink(GroupMember member) {
@@ -1655,7 +1657,7 @@
     }
 
     private static String docEnumLink(EnumReference enumRef) {
-      return "{@link " + simpleName(enumRef.enumClass) + "#" + enumRef.enumValue + "}";
+      return "{@link " + getUnqualifiedName(enumRef.enumClass) + "#" + enumRef.enumValue + "}";
     }
 
     private static String docEnumLinkList(EnumReference... values) {
@@ -1835,7 +1837,7 @@
       withIndent(
           () -> {
             generateAnnotationConstants(USED_BY_NATIVE);
-            println("// Content is the same as " + simpleName(USED_BY_REFLECTION) + ".");
+            println("// Content is the same as " + getUnqualifiedName(USED_BY_REFLECTION) + ".");
           });
       println("}");
       println();
@@ -2088,7 +2090,7 @@
           () -> {
             generateAnnotationConstants(CLASS_NAME_PATTERN);
             classNamePatternFullNameGroup().generateConstants(this);
-            classNamePatternSimpleNameGroup().generateConstants(this);
+            classNamePatternUnqualifiedNameGroup().generateConstants(this);
             classNamePatternPackageGroup().generateConstants(this);
           });
       println("}");
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringVerticallyMergedClassTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringVerticallyMergedClassTest.java
new file mode 100644
index 0000000..022d375
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringVerticallyMergedClassTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.util.HashMap;
+import java.util.Map;
+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 IdentifierNameStringVerticallyMergedClassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(A.class)
+        .addKeepRules("-identifiernamestring class " + Main.class.getTypeName() + " { <fields>; }")
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(I.class).assertNoOtherClassesMerged())
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class Main {
+
+    @NeverPropagateValue
+    static String s = "com.android.tools.r8.naming.IdentifierNameStringVerticallyMergedClassTest$I";
+
+    public static void main(String[] args) throws Exception {
+      String key = System.currentTimeMillis() > 0 ? s : args[0];
+      A a = (A) createInstanceMap().get(key);
+      System.out.println(a);
+    }
+
+    static Map<String, Object> createInstanceMap() {
+      Map<String, Object> map = new HashMap<>();
+      map.put(I.class.getName(), new A());
+      return map;
+    }
+  }
+
+  interface I {}
+
+  public static class A implements I {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress339371242.java b/src/test/java/com/android/tools/r8/regress/Regress339371242.java
new file mode 100644
index 0000000..2201a1a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress339371242.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.regress;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress339371242 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withAllApiLevels().build();
+  }
+
+  public Regress339371242(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, WithLibraryFinalizer.class)
+        .addLibraryClasses(LibraryClassWithFinalizer.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            codeInspector -> {
+              ClassSubject clazz = codeInspector.clazz(TestClass.class);
+              assertThat(clazz, isPresent());
+              assertThat(clazz.uniqueFieldWithOriginalName("handler"), isPresent());
+            });
+  }
+
+  public static class LibraryClassWithFinalizer {
+
+    @Override
+    protected void finalize() throws Throwable {
+      super.finalize();
+    }
+  }
+
+  public static class WithLibraryFinalizer extends LibraryClassWithFinalizer {}
+
+  public static class TestClass {
+    private final WithLibraryFinalizer handler;
+
+    public static void main(String[] args) {
+      new TestClass().foo();
+    }
+
+    public TestClass() {
+      handler = new WithLibraryFinalizer();
+    }
+
+    public void foo() {
+      System.out.println("ab");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java
new file mode 100644
index 0000000..a81e419
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// Regression test for b/342802978 after R8 class inlining.
+@RunWith(Parameterized.class)
+public class ArrayCloneInDefaultInterfaceMethodAfterClassInliningTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestParameters.builder()
+        .withAllRuntimes()
+        .withApiLevel(apiLevelWithDefaultInterfaceMethodsSupport())
+        .build();
+  }
+
+  @Parameter public TestParameters parameters;
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class, B.class, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(I.class)
+        .addProgramClasses(I.class, A.class, B.class, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> r) {
+    r.assertSuccessWithOutputLines("0");
+  }
+
+  interface I {
+
+    default String[] myClone(String[] strings) {
+      return new B().myClone(strings);
+    }
+  }
+
+  static class A implements I {}
+
+  static class B {
+
+    @NeverInline
+    public String[] myClone(String[] strings) {
+      return strings.clone();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I i = System.nanoTime() > 0 ? new A() : null;
+      System.out.println(i.myClone(args).length);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java
new file mode 100644
index 0000000..edae17d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// Regression test for b/342802978, but with R8 giving rise to the issue after inlining.
+@RunWith(Parameterized.class)
+public class ArrayCloneInStaticInterfaceMethodAfterMethodInliningTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestParameters.builder()
+        .withAllRuntimes()
+        .withApiLevel(apiLevelWithDefaultInterfaceMethodsSupport())
+        .build();
+  }
+
+  @Parameter public TestParameters parameters;
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .setMinApi(parameters)
+        .addKeepMainRule(TestClass.class)
+        .addProgramClasses(I.class, A.class, TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkOutput);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> r) {
+    r.assertSuccessWithOutputLines("0");
+  }
+
+  interface I {
+
+    static String[] myClone(String[] strings) {
+      try {
+        return A.inlinedClone(strings);
+      } catch (RuntimeException e) {
+        // Extra code to avoid simple inlining.
+        System.out.println("Unexpected exception: " + e);
+        throw e;
+      }
+    }
+  }
+
+  static class A {
+
+    public static String[] inlinedClone(String[] strings) {
+      return strings.clone();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      int count = 0;
+      // Repeated calls to avoid single-caller inlining of the interface method.
+      count += I.myClone(args).length;
+      count += I.myClone(args).length;
+      count += I.myClone(args).length;
+      count += I.myClone(args).length;
+      count += I.myClone(args).length;
+      System.out.println(count);
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index cb49489..2061f8f 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.CustomConversionVersion;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -26,6 +27,7 @@
 import com.android.tools.r8.graph.AssemblyWriter;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
@@ -1601,6 +1603,11 @@
           "Unexpected configuration with identical test and test-base path" + resolveTestPath);
     }
     if (foundTestPath && foundBasePath) {
+      if (identicalClassesIgnoringClassFileVersion(resolveTestPath, resolveBasePath)) {
+        System.out.println(
+            "Ambiguous yet identical test file: " + resolveTestPath + " and " + resolveBasePath);
+        return resolveBasePath;
+      }
       throw new RuntimeException(
           "Ambiguous test file: " + resolveTestPath + " and " + resolveBasePath);
     }
@@ -1617,6 +1624,27 @@
             + resolveBasePath);
   }
 
+  private static boolean identicalClassesIgnoringClassFileVersion(
+      Path resolveBasePath, Path resolveTestPath) {
+    InternalOptions options = new InternalOptions();
+
+    try {
+      AndroidApp input1 = AndroidApp.builder().addProgramFiles(resolveBasePath).build();
+      DexApplication app1 = new ApplicationReader(input1, options, Timing.empty()).read();
+      DexProgramClass clazz1 = app1.classes().iterator().next();
+      clazz1.downgradeInitialClassFileVersion(CfVersion.V1_8);
+
+      AndroidApp input2 = AndroidApp.builder().addProgramFiles(resolveTestPath).build();
+      DexApplication app2 = new ApplicationReader(input2, options, Timing.empty()).read();
+      DexProgramClass clazz2 = app2.classes().iterator().next();
+      clazz2.downgradeInitialClassFileVersion(CfVersion.V1_8);
+
+      return clazz1.compareTo(clazz2) == 0;
+    } catch (Exception e) {
+      return false;
+    }
+  }
+
   public static Collection<Path> getClassFilesForInnerClasses(Collection<Class<?>> classes)
       throws IOException {
     return getClassFilesForInnerClasses(computeLegacyOrGradleSpecifiedLocation(), classes);
diff --git a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
index 3e52ba7..9dcfa97 100644
--- a/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
+++ b/src/test/testbase/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.benchmarks;
 
 import com.android.tools.r8.utils.StringUtils;
+import java.io.PrintStream;
 
 public interface BenchmarkResults {
   // Append a runtime result. This may be summed or averaged depending on the benchmark set up.
@@ -19,7 +20,9 @@
   // This will throw if called on a benchmark without sub-benchmarks.
   BenchmarkResults getSubResults(String name);
 
-  void printResults(ResultMode resultMode);
+  void printResults(ResultMode resultMode, boolean failOnCodeSizeDifferences);
+
+  void writeResults(PrintStream out);
 
   static String prettyTime(long nanoTime) {
     return "" + (nanoTime / 1000000) + " ms";
diff --git a/tools/dex2oat.py b/tools/dex2oat.py
index 8df8f86..58b2418 100755
--- a/tools/dex2oat.py
+++ b/tools/dex2oat.py
@@ -152,9 +152,10 @@
     append_dex2oat_verbose_flags(options, cmd)
     utils.PrintCmd(cmd)
     subprocess.check_call(cmd)
-    cmd = adb_cmd(serial, 'logcat', '-d', '-s', 'dex2oat')
+    cmd = adb_cmd(serial, 'logcat', '-d', '-s', 'dex2oat', 'dex2oat_original')
     utils.PrintCmd(cmd)
     subprocess.check_call(cmd)
+
     return 0
 
 
diff --git a/tools/historic_run.py b/tools/historic_run.py
index 37c3dae..9822648 100755
--- a/tools/historic_run.py
+++ b/tools/historic_run.py
@@ -34,6 +34,10 @@
     result.add_option('--output',
                       default='build',
                       help='Directory where to output results')
+    result.add_option('--timeout',
+                      default=1000,
+                      help='Timeout in seconds (-1 for no timeout)',
+                      type=int)
     return result.parse_args(argv)
 
 
@@ -52,16 +56,30 @@
     def __repr__(self):
         return self.__str__()
 
+    def hash(self):
+        return self.git_hash
+
+    def title(self):
+        result = subprocess.check_output(
+            ['git', 'show-branch', '--no-name', self.git_hash]).decode('utf-8')
+        return result.strip()
+
+    def author_name(self):
+        result = subprocess.check_output([
+            'git', 'show', '--no-notes', '--no-patch', '--pretty=%an',
+            self.git_hash
+        ]).decode('utf-8')
+        return result.strip()
+
+    def committer_timestamp(self):
+        return self.timestamp
+
 
 def git_commit_from_hash(hash):
-    commit_timestamp = subprocess.check_output([
-        'git',
-        'show',
-        '--no-patch',
-        '--no-notes',
-        '--pretty=\'%ct\'',
-         hash
-    ]).decode().strip().strip('\'')
+    commit_timestamp_str = subprocess.check_output(
+        ['git', 'show', '--no-patch', '--no-notes', '--pretty=%ct',
+         hash]).decode('utf-8').strip()
+    commit_timestamp = int(commit_timestamp_str)
     destination_dir = '%s/%s/' % (MASTER_COMMITS, hash)
     destination = '%s%s' % (destination_dir, 'r8.jar')
     commit = GitCommit(hash, destination_dir, destination, commit_timestamp)
@@ -70,9 +88,12 @@
 
 def enumerate_git_commits(top, bottom):
     if bottom is None:
-        output = subprocess.check_output(['git', 'rev-list', '--first-parent', '-n', 1000, top])
+        output = subprocess.check_output(
+            ['git', 'rev-list', '--first-parent', '-n', 1000, top])
     else:
-        output = subprocess.check_output(['git', 'rev-list', '--first-parent', '%s^..%s' % (bottom, top)])
+        output = subprocess.check_output(
+            ['git', 'rev-list', '--first-parent',
+             '%s^..%s' % (bottom, top)])
     commits = []
     for c in output.decode().splitlines():
         commit_hash = c.strip()
@@ -81,8 +102,8 @@
 
 
 def get_available_commits(commits):
-    cloud_commits = subprocess.check_output(
-        ['gsutil.py', 'ls', MASTER_COMMITS]).decode().splitlines()
+    cloud_commits = subprocess.check_output(['gsutil.py', 'ls', MASTER_COMMITS
+                                            ]).decode().splitlines()
     available_commits = []
     for commit in commits:
         if commit.destination_dir in cloud_commits:
@@ -129,7 +150,7 @@
     count = 0
     for index in commit_permutations:
         count += 1
-        print('Running commit %s out of %s' % (count, len(commits)))
+        print('\nRunning commit %s out of %s' % (count, len(commits)))
         commit = commits[index]
         if not utils.cloud_storage_exists(commit.destination):
             # We may have a directory, but no r8.jar
@@ -164,11 +185,13 @@
 
 
 def run_cmd(options, commit):
-    cmd = [options.cmd, commit.git_hash]
+    cmd = options.cmd.split(' ')
+    cmd.append(commit.git_hash)
     output_path = options.output or 'build'
     time_commit = '%s_%s' % (commit.timestamp, commit.git_hash)
     time_commit_path = os.path.join(output_path, time_commit)
     print(' '.join(cmd))
+    status = None
     if not options.dry_run:
         if not os.path.exists(time_commit_path):
             os.makedirs(time_commit_path)
@@ -177,15 +200,20 @@
         with open(stdout_path, 'w') as stdout:
             with open(stderr_path, 'w') as stderr:
                 process = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
-                timeout = 1000
-                while process.poll() is None and timeout > 0:
+                timeout = options.timeout
+                while process.poll() is None and timeout != 0:
                     time.sleep(1)
                     timeout -= 1
                 if process.poll() is None:
                     process.kill()
                     print("Task timed out")
                     stderr.write("timeout\n")
-    print('Wrote outputs to: %s' % time_commit_path)
+                    status = 'TIMED OUT'
+                else:
+                    returncode = process.returncode
+                    status = 'SUCCESS' if returncode == 0 else f'FAILED ({returncode})'
+    print(f'Wrote outputs to: {time_commit_path}')
+    print(status)
 
 
 def main(argv):
diff --git a/tools/perf.py b/tools/perf.py
index 3762c5d..0186435 100755
--- a/tools/perf.py
+++ b/tools/perf.py
@@ -4,14 +4,24 @@
 # BSD-style license that can be found in the LICENSE file.
 
 import argparse
+import compiledump
+import json
 import os
+import shutil
 import subprocess
 import sys
 
+import upload_benchmark_data_to_google_storage
 import utils
 
-DEFAULT_ITERATIONS = 10
-BUCKET = "r8-test-results"
+BUCKET = "r8-perf-results"
+SAMPLE_BENCHMARK_RESULT_JSON = {
+    'benchmark_name': '<benchmark_name>',
+    'results': [{
+        'code_size': 0,
+        'runtime': 0
+    }]
+}
 
 # Result structure on cloud storage
 # gs://bucket/benchmark_results/APP/TARGET/GIT_HASH/results
@@ -19,68 +29,174 @@
 # where results simply contains the result lines and
 # meta contains information about the execution (machine)
 
+
 def ParseOptions():
     result = argparse.ArgumentParser()
     result.add_argument('--app',
-                        help='Specific app to measure.',
-                        default='NowInAndroidApp')
+                        help='Specific app(s) to measure.',
+                        action='append')
+    result.add_argument('--iterations',
+                        help='How many times run_benchmark is run.',
+                        type=int,
+                        default=1)
+    result.add_argument('--iterations-inner',
+                        help='How many iterations to run inside run_benchmark.',
+                        type=int,
+                        default=10)
+    result.add_argument('--outdir',
+                        help='Output directory for running locally.')
+    result.add_argument('--skip-if-output-exists',
+                        help='Skip if output exists.',
+                        action='store_true',
+                        default=False)
     result.add_argument('--target',
                         help='Specific target to run on.',
                         default='r8-full',
                         choices=['d8', 'r8-full', 'r8-force', 'r8-compat'])
-    result.add_argument('--iterations',
-                        help='How many iterations to run.',
-                        type=int,
-                        default=DEFAULT_ITERATIONS)
-    return result.parse_known_args()
+    result.add_argument('--verbose',
+                        help='To enable verbose logging.',
+                        action='store_true',
+                        default=False)
+    result.add_argument('--version',
+                        '-v',
+                        help='Use R8 hash for the run (default local build)',
+                        default=None)
+    options, args = result.parse_known_args()
+    options.apps = options.app or ['NowInAndroidApp', 'TiviApp']
+    options.quiet = not options.verbose
+    del options.app
+    return options, args
 
 
-def ParseOutput(output, options, log_array):
-    for line in output.decode('utf-8').splitlines():
-        print("   -- " + line)
-        # Output lines look like:
-        #    Benchmark results for NowInAndroidApp on target r8-full
-        #      warmup reporting mode: average
-        #      warmup iterations: 1
-        #      warmup total time: 58580 ms
-        #      benchmark reporting mode: average
-        #      benchmark iterations: 1
-        #      benchmark total time: 39613 ms
-        #    NowInAndroidApp(RunTimeRaw): 39154 ms
-        #    NowInAndroidApp(CodeSize): 5102196
-        if line.startswith(options.app + "("):
-            log_array.append(line)
+def MergeBenchmarkResultJsonFiles(benchmark_result_json_files):
+    merged_benchmark_result_json = None
+    for benchmark_result_json_file in benchmark_result_json_files:
+        benchmark_result_json = ParseBenchmarkResultJsonFile(
+            benchmark_result_json_file)
+        if merged_benchmark_result_json is None:
+            merged_benchmark_result_json = benchmark_result_json
+        else:
+            MergeBenchmarkResultJsonFile(merged_benchmark_result_json,
+                                         benchmark_result_json)
+    return merged_benchmark_result_json
 
 
-def GetGSLocation(app, target, filename):
-    return "gs://%s/%s/%s/%s/%s" % (BUCKET, app, target,
-                                    utils.get_HEAD_sha1(), filename)
+def MergeBenchmarkResultJsonFile(merged_benchmark_result_json,
+                                 benchmark_result_json):
+    assert benchmark_result_json.keys() == SAMPLE_BENCHMARK_RESULT_JSON.keys()
+    assert merged_benchmark_result_json[
+        'benchmark_name'] == benchmark_result_json['benchmark_name']
+    merged_benchmark_result_json['results'].extend(
+        benchmark_result_json['results'])
 
+
+def ParseBenchmarkResultJsonFile(result_json_file):
+    with open(result_json_file, 'r') as f:
+        lines = f.readlines()
+        return json.loads(''.join(lines))
+
+
+def GetArtifactLocation(app, target, version, filename):
+    version_or_head = version or utils.get_HEAD_sha1()
+    return f'{app}/{target}/{version_or_head}/{filename}'
+
+
+def GetGSLocation(filename, bucket=BUCKET):
+    return f'gs://{bucket}/{filename}'
+
+
+def ArchiveOutputFile(file, dest, bucket=BUCKET, header=None, outdir=None):
+    if outdir:
+        dest_in_outdir = os.path.join(outdir, dest)
+        os.makedirs(os.path.dirname(dest_in_outdir), exist_ok=True)
+        shutil.copyfile(file, dest_in_outdir)
+    else:
+        utils.upload_file_to_cloud_storage(file,
+                                           GetGSLocation(dest, bucket=bucket),
+                                           header=header)
+
+
+# Usage with historic_run.py:
+# ./tools/historic_run.py
+#     --cmd "perf.py --skip-if-output-exists --version"
+#     --timeout -1
+#     --top 3373fd18453835bf49bff9f02523a507a2ebf317
+#     --bottom 7486f01e0622cb5935b77a92b59ddf1ca8dbd2e2
 def main():
-    (options, args) = ParseOptions()
-    cmd = ['tools/run_benchmark.py', '--target', options.target,
-           '--benchmark', options.app]
-    log_array = []
-    # Build and warmup
-    output = subprocess.check_output(cmd)
-    ParseOutput(output, options, log_array)
-    cmd.append('--no-build')
-    for i in range(options.iterations):
-        output = subprocess.check_output(cmd)
-        ParseOutput(output, options, log_array)
+    options, args = ParseOptions()
     with utils.TempDir() as temp:
-        result_file = os.path.join(temp, "result_file")
-        with open(result_file, 'w') as f:
-            for l in log_array:
-                f.write(l + "\n")
-        utils.upload_file_to_cloud_storage(result_file,
-            GetGSLocation(options.app, options.target, 'results'))
-        if os.environ.get('SWARMING_BOT_ID'):
-            meta_file = os.path.join(temp, "meta")
-            with open(meta_file, 'w') as f:
-                f.write("Produced by: " + os.environ.get('SWARMING_BOT_ID'))
-            utils.upload_file_to_cloud_storage(meta_file,
-                GetGSLocation(options.app, options.target, 'meta'))
+        if options.version:
+            # Download r8.jar once instead of once per run_benchmark.py invocation.
+            download_options = argparse.Namespace(no_build=True, nolib=True)
+            r8jar = compiledump.download_distribution(options.version,
+                                                      download_options, temp)
+
+        for app in options.apps:
+            if options.skip_if_output_exists:
+                if options.outdir:
+                    raise NotImplementedError
+                output = GetGSLocation(
+                    GetArtifactLocation(app, options.target, options.version,
+                                        'result.json'))
+                if utils.cloud_storage_exists(output):
+                    print(f'Skipping run, {output} already exists.')
+                    continue
+
+            base_cmd = [
+                'tools/run_benchmark.py', '--benchmark', app, '--target',
+                options.target
+            ]
+            if options.verbose:
+                base_cmd.append('--verbose')
+            if options.version:
+                base_cmd.extend([
+                    '--version', options.version, '--version-jar', r8jar,
+                    '--nolib'
+                ])
+
+            # Build
+            utils.Print(f'Preparing {app}', quiet=options.quiet)
+            build_cmd = base_cmd + ['--iterations', '0']
+            subprocess.check_output(build_cmd)
+
+            # Run benchmark.
+            benchmark_result_json_files = []
+            for i in range(options.iterations):
+                utils.Print(f'Benchmarking {app} ({i+1}/{options.iterations})',
+                            quiet=options.quiet)
+                benchhmark_result_file = os.path.join(temp, f'result_file_{i}')
+                iteration_cmd = base_cmd + [
+                    '--iterations',
+                    str(options.iterations_inner), '--output',
+                    benchhmark_result_file, '--no-build'
+                ]
+                subprocess.check_output(iteration_cmd)
+                benchmark_result_json_files.append(benchhmark_result_file)
+
+            # Merge results and write output.
+            result_file = os.path.join(temp, 'result_file')
+            with open(result_file, 'w') as f:
+                json.dump(
+                    MergeBenchmarkResultJsonFiles(benchmark_result_json_files),
+                    f)
+            ArchiveOutputFile(result_file,
+                              GetArtifactLocation(app, options.target,
+                                                  options.version,
+                                                  'result.json'),
+                              outdir=options.outdir)
+
+            # Write metadata.
+            if utils.is_bot():
+                meta_file = os.path.join(temp, "meta")
+                with open(meta_file, 'w') as f:
+                    f.write("Produced by: " + os.environ.get('SWARMING_BOT_ID'))
+                ArchiveOutputFile(meta_file,
+                                  GetArtifactLocation(app, options.target,
+                                                      options.version, 'meta'),
+                                  outdir=options.outdir)
+
+    if utils.is_bot():
+        upload_benchmark_data_to_google_storage.run()
 
 
 if __name__ == '__main__':
diff --git a/tools/perf/benchmark_data.json b/tools/perf/benchmark_data.json
new file mode 100644
index 0000000..9d18466
--- /dev/null
+++ b/tools/perf/benchmark_data.json
@@ -0,0 +1,24 @@
+[{
+  "author": "Christoffer Adamsen",
+  "hash": "e6a33325a4a4bac599ffce1f75cab8505a35d02a",
+  "submitted": "Thu Jun 06 13:07:44 2024 +0200",
+  "title": "Allow compiling CompileDumpCompatR8 in isolation",
+  "benchmarks": {
+    "NowInAndroidApp": {
+      "benchmark_name": "NowInAndroidApp",
+      "results": [
+        { "code_size": 42, "runtime": 1 },
+        { "code_size": 42, "runtime": 2 },
+        { "code_size": 42, "runtime": 3 }
+      ]
+    },
+    "TiviApp": {
+      "benchmark_name": "TiviApp",
+      "results": [
+        { "code_size": 84, "runtime": 4 },
+        { "code_size": 84, "runtime": 5 },
+        { "code_size": 84, "runtime": 6 }
+      ]
+    }
+  }
+}]
\ No newline at end of file
diff --git a/tools/perf/index.html b/tools/perf/index.html
new file mode 100644
index 0000000..8d3089c
--- /dev/null
+++ b/tools/perf/index.html
@@ -0,0 +1,337 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>R8 perf</title>
+</head>
+<body>
+  <select id="benchmark-selector"></select>
+  <div>
+      <canvas id="myChart"></canvas>
+  </div>
+  <div>
+    <div style="float: left; width: 50%">
+      <button type="button" id="show-more-left" disabled>⇐</button>
+      <button type="button" id="show-less-left">⇒</button>
+    </div>
+    <div style="float: left; text-align: right; width: 50%">
+      <button type="button" id="show-less-right">⇐</button>
+      <button type="button" id="show-more-right" disabled>⇒</button>
+    </div>
+  </div>
+  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
+  <script type="module">
+    // Utility methods.
+    Array.prototype.any = function(predicate) {
+      for (const element of this.values()) {
+        if (predicate(element)) {
+          return true;
+        }
+      }
+      return false;
+    };
+    Array.prototype.first = function() {
+      return this[0];
+    };
+    Array.prototype.avg = function() {
+      return this.reduce(function(x, y) { return x + y; }, 0) / this.length;
+    };
+    Array.prototype.min = function() {
+      return this.reduce(function(x, y) { return x === null ? y : Math.min(x, y); }, null);
+    };
+    Array.prototype.reverseInPlace = function() {
+      for (var i = 0; i < Math.floor(this.length / 2); i++) {
+        var temp = this[i];
+        this[i] = this[this.length - i - 1];
+        this[this.length - i - 1] = temp;
+      }
+    };
+    Number.prototype.ns_to_s = function() {
+      const seconds = this/10E8;
+      const seconds_with_one_decimal = Math.round(seconds*10)/10;
+      return seconds;
+    };
+
+    // Import and reverse commits so that newest are last.
+    import commits from "./benchmark_data.json" with { type: "json" };
+    commits.reverseInPlace()
+
+    // Amend the commits with their unique index.
+    for (var i = 0; i < commits.length; i++) {
+      commits[i].index = i;
+    }
+
+    // DOM references.
+    const benchmarkSelector = document.getElementById('benchmark-selector')
+    const canvas = document.getElementById('myChart');
+    const showMoreLeft = document.getElementById('show-more-left');
+    const showLessLeft = document.getElementById('show-less-left');
+    const showLessRight = document.getElementById('show-less-right');
+    const showMoreRight = document.getElementById('show-more-right');
+
+    // Initialize benchmark selector.
+    const benchmarks = new Set();
+    for (const commit of commits.values()) {
+      for (const benchmark in commit.benchmarks) {
+          benchmarks.add(benchmark)
+      }
+    }
+    var selectedBenchmark = window.location.hash.substring(1)
+    if (!benchmarks.has(selectedBenchmark)) {
+      selectedBenchmark = benchmarks.values().next().value;
+    }
+    for (const benchmark of benchmarks.values()) {
+      const opt = document.createElement('option');
+      opt.value = benchmark;
+      opt.innerHTML = benchmark;
+      benchmarkSelector.appendChild(opt);
+      if (benchmark == selectedBenchmark) {
+        benchmarkSelector.selectedIndex = benchmarkSelector.options.length - 1
+      }
+    }
+    benchmarkSelector.onchange = function(event) {
+      selectedBenchmark =
+          benchmarkSelector.options[benchmarkSelector.selectedIndex].value;
+      updateChart();
+      window.location.hash = selectedBenchmark;
+    };
+
+    // Chart data provider.
+    function getData(start = 0, end = commits.length) {
+      const filteredCommits =
+          commits
+              .slice(start, end);
+              //.filter(
+              //    commit =>
+              //        selectedBenchmark in commit.benchmarks
+              //            && commit.benchmarks[selectedBenchmark].results.length > 0);
+      const labels = filteredCommits.map((c, i) => i);
+      const codeSizeData =
+          filteredCommits.map(
+              (c, i) =>
+                  selectedBenchmark in filteredCommits[i].benchmarks
+                      ? filteredCommits[i]
+                          .benchmarks[selectedBenchmark]
+                          .results
+                          .first()
+                          .code_size
+                      : NaN);
+      const codeSizeScatterData = [];
+      for (const commit of filteredCommits.values()) {
+        if (!(selectedBenchmark in commit.benchmarks)) {
+          continue;
+        }
+        const codeSizes =
+            commit.benchmarks[selectedBenchmark].results.map(result => result.code_size)
+        const expectedCodeSize = codeSizes.first();
+        if (codeSizes.any(codeSize => codeSize != expectedCodeSize)) {
+          const seen = new Set();
+          seen.add(expectedCodeSize);
+          for (const codeSize of codeSizes.values()) {
+            if (!seen.has(codeSize)) {
+              codeSizeScatterData.push({ x: commit.index, y: codeSize });
+              seen.add(codeSize);
+            }
+          }
+        }
+      }
+      const runtimeData =
+          filteredCommits.map(
+              (c, i) =>
+                  selectedBenchmark in filteredCommits[i].benchmarks
+                      ? filteredCommits[i]
+                          .benchmarks[selectedBenchmark]
+                          .results
+                          .map(result => result.runtime)
+                          .min()
+                          .ns_to_s()
+                      : NaN);
+      const runtimeScatterData = [];
+      for (const commit of filteredCommits.values()) {
+        if (!(selectedBenchmark in commit.benchmarks)) {
+          continue;
+        }
+        const runtimes =
+            commit.benchmarks[selectedBenchmark].results.map(result => result.runtime)
+        for (const runtime of runtimes.values()) {
+          runtimeScatterData.push({ x: commit.index, y: runtime.ns_to_s() });
+        }
+      }
+
+      const skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;
+      return {
+        labels: labels,
+        datasets: [
+          {
+            type: 'line',
+            label: 'Code size',
+            data: codeSizeData,
+            tension: 0.1,
+            segment: {
+              borderColor: ctx =>
+                  skipped(ctx, 'rgba(54, 162, 235, 0.5)'),
+              borderDash: ctx => skipped(ctx, [6, 6]),
+            },
+            spanGaps: true
+          },
+          {
+            type: 'scatter',
+            label: 'Nondeterminism',
+            data: codeSizeScatterData,
+            pointBackgroundColor: 'red'
+          },
+          {
+            type: 'line',
+            label: 'Runtime',
+            data: runtimeData,
+            tension: 0.1,
+            yAxisID: 'y2',
+            segment: {
+              borderColor: ctx =>
+                  skipped(ctx, 'rgba(255, 160, 64, 0.5)'),
+              borderDash: ctx => skipped(ctx, [6, 6]),
+            },
+            spanGaps: true
+          },
+          {
+            type: 'scatter',
+            label: 'Runtime variance',
+            data: runtimeScatterData,
+            yAxisID: 'y2'
+          }
+        ],
+      };
+    }
+
+    // Chart options.
+    const options = {
+      onHover: (event, chartElement) =>
+          event.native.target.style.cursor =
+              chartElement[0] ? 'pointer' : 'default',
+      plugins: {
+        tooltip: {
+          callbacks: {
+            title: (context) => {
+              const elementInfo = context[0];
+              var commit;
+              if (elementInfo.dataset.type == 'line') {
+                commit = commits[elementInfo.dataIndex];
+              } else {
+                console.assert(elementInfo.dataset.type == 'scatter');
+                commit = commits[elementInfo.raw.x];
+              }
+              return commit.title;
+            },
+            footer: (context) => {
+              const elementInfo = context[0];
+              var commit;
+              if (elementInfo.dataset.type == 'line') {
+                commit = commits[elementInfo.dataIndex];
+              } else {
+                console.assert(elementInfo.dataset.type == 'scatter');
+                commit = commits[elementInfo.raw.x];
+              }
+              return `Author: ${commit.author}\n`
+                  + `Submitted: ${new Date(commit.submitted * 1000).toLocaleString()}\n`
+                  + `Hash: ${commit.hash}`;
+            }
+          }
+        }
+      },
+      responsive: true,
+      scales: {
+        x: {},
+        y: {
+          position: 'left',
+          title: {
+            display: true,
+            text: 'Code size (bytes)'
+          }
+        },
+        y2: {
+          position: 'right',
+          title: {
+            display: true,
+            text: 'Runtime (seconds)'
+          }
+        }
+      }
+    };
+
+    // Create chart.
+    const myChart = new Chart(canvas, {
+      data: getData(),
+      options: options
+    });
+
+    // Setup click handler.
+    canvas.onclick = function (event) {
+      const points =
+          myChart.getElementsAtEventForMode(
+              event, 'nearest', { intersect: true }, true);
+      if (points.length > 0) {
+        const point = points[0];
+        const commit = commits[point.index];
+        window.open('https://r8.googlesource.com/r8/+/' + commit.hash, '_blank');
+      }
+    };
+
+    // Setup chart navigation.
+    var left = 0;
+    var right = commits.length;
+
+    showMoreLeft.onclick = function (event) {
+      if (left == 0) {
+        return;
+      }
+      const currentSize = right - left;
+      left = left - currentSize;
+      if (left < 0) {
+        left = 0;
+      }
+      updateChart();
+    };
+
+    showLessLeft.onclick = function (event) {
+      const currentSize = right - left;
+      left = left + Math.floor(currentSize / 2);
+      if (left >= right) {
+        left = right - 1;
+      }
+      updateChart();
+    };
+
+    showLessRight.onclick = function (event) {
+      if (right == 0) {
+        return;
+      }
+      const currentSize = right - left;
+      right = right - Math.floor(currentSize / 2);
+      if (right < left) {
+        right = left;
+      }
+      updateChart();
+    };
+
+    showMoreRight.onclick = function (event) {
+      const currentSize = right - left;
+      right = right + currentSize;
+      if (right > commits.length) {
+        right = commits.length;
+      }
+      updateChart();
+    };
+
+    function updateChart() {
+      console.assert(left <= right);
+      const newData = getData(left, right);
+      Object.assign(myChart.data, newData);
+      myChart.update();
+      showMoreLeft.disabled = left == 0;
+      showLessLeft.disabled = left == right - 1;
+      showLessRight.disabled = left == right - 1;
+      showMoreRight.disabled = right == commits.length;
+    }
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
index 3a1efbf..6bf0882 100755
--- a/tools/run_benchmark.py
+++ b/tools/run_benchmark.py
@@ -63,6 +63,12 @@
                         help='Enable assertions when running',
                         default=False,
                         action='store_true')
+    result.add_argument('--iterations',
+                        '-i',
+                        help='Number of iterations to run',
+                        type=int)
+    result.add_argument('--output',
+                        help='Output path where to write the result')
     result.add_argument('--print-times',
                         help='Print timing information from r8',
                         default=False,
@@ -72,17 +78,35 @@
         '-v',
         help='Use R8 version/hash for the run (default local build)',
         default=None)
+    result.add_argument(
+        '--version-jar',
+        help='The r8.jar corresponding to the version given at --version.',
+        default=None)
     result.add_argument('--temp',
                         help='A directory to use for temporaries and outputs.',
                         default=None)
-    return result.parse_known_args(argv)
+    result.add_argument('--verbose',
+                        help='To enable verbose logging.',
+                        action='store_true',
+                        default=False)
+    options, args = result.parse_known_args(argv)
+    options.quiet = not options.verbose
+    # We must download the non-lib distribution when running with a specific
+    # version, since BenchmarkMainEntryRunner is using R8 internals.
+    # TODO(b/346477461): Look into removing this limitation.
+    assert options.version is None or options.nolib
+    return options, args
 
 
 def main(argv, temp):
-    (options, args) = parse_options(argv)
+    options, args = parse_options(argv)
+
+    if options.output:
+        options.output = os.path.abspath(options.output)
 
     if options.temp:
         temp = options.temp
+        os.makedirs(temp, exist_ok=True)
 
     if options.golem:
         options.no_build = True
@@ -92,11 +116,14 @@
 
     if options.nolib:
         testBuildTargets = [
-            utils.GRADLE_TASK_TEST_JAR, utils.GRADLE_TASK_TEST_DEPS_JAR
+            utils.GRADLE_TASK_TEST_JAR, utils.GRADLE_TASK_TEST_DEPS_JAR,
+            utils.GRADLE_TASK_TEST_UNZIP_TESTBASE
         ]
         buildTargets = [utils.GRADLE_TASK_R8] + testBuildTargets
         r8jar = utils.R8_JAR
-        testjars = [utils.R8_TESTS_JAR, utils.R8_TESTS_DEPS_JAR, utils.R8_TESTBASE_JAR]
+        testjars = [
+            utils.R8_TESTS_JAR, utils.R8_TESTS_DEPS_JAR, utils.R8_TESTBASE_JAR
+        ]
     else:
         testBuildTargets = GOLEM_BUILD_TARGETS_TESTS
         buildTargets = GOLEM_BUILD_TARGETS
@@ -107,11 +134,11 @@
             os.path.join(utils.R8LIB_TESTBASE_JAR)
         ]
 
-    if options.version:
+    if options.version or options.version_jar:
         # r8 is downloaded so only test jar needs to be built.
         buildTargets = testBuildTargets
-        r8jar = compiledump.download_distribution(options.version,
-                                                  options.nolib, temp)
+        r8jar = options.version_jar or compiledump.download_distribution(
+            options.version, options, temp)
 
     if not options.no_build:
         gradle.RunGradle(buildTargets + ['-Pno_internal'])
@@ -127,7 +154,11 @@
 
 def run(options, r8jar, testjars):
     jdkhome = get_jdk_home(options, options.benchmark)
-    cmd = [jdk.GetJavaExecutable(jdkhome)]
+    cmd = [
+        jdk.GetJavaExecutable(jdkhome), '-Xms8g', '-Xmx8g',
+        '-XX:+TieredCompilation', '-XX:TieredStopAtLevel=4',
+        '-DBENCHMARK_IGNORE_CODE_SIZE_DIFFERENCES'
+    ]
     if options.enable_assertions:
         cmd.append('-ea')
     if options.print_times:
@@ -137,6 +168,12 @@
             f'-DTEST_DATA_LOCATION={utils.REPO_ROOT}/d8_r8/test_modules/tests_java_8/build/classes/java/test',
             f'-DTESTBASE_DATA_LOCATION={utils.REPO_ROOT}/d8_r8/test_modules/testbase/build/classes/java/main',
         ])
+    if options.iterations is not None:
+        if options.iterations == 0:
+            return
+        cmd.append(f'-DBENCHMARK_ITERATIONS={options.iterations}')
+    if options.output:
+        cmd.append(f'-DBENCHMARK_OUTPUT={options.output}')
     cmd.extend(['-cp', ':'.join([r8jar] + testjars)])
     cmd.extend([
         'com.android.tools.r8.benchmarks.BenchmarkMainEntryRunner',
@@ -146,6 +183,7 @@
         # repository root as an argument. The runner can then setup dependencies.
         'golem' if options.golem else utils.REPO_ROOT,
     ])
+    utils.PrintCmd(cmd, quiet=options.quiet)
     return subprocess.check_call(cmd)
 
 
diff --git a/tools/upload_benchmark_data_to_google_storage.py b/tools/upload_benchmark_data_to_google_storage.py
new file mode 100755
index 0000000..4fb9444
--- /dev/null
+++ b/tools/upload_benchmark_data_to_google_storage.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import historic_run
+import json
+import os
+import perf
+import time
+import utils
+
+import sys
+
+APPS = ['NowInAndroidApp', 'TiviApp']
+TARGETS = ['r8-full']
+NUM_COMMITS = 250
+
+INDEX_HTML = os.path.join(utils.TOOLS_DIR, 'perf/index.html')
+
+
+def DownloadCloudBucket(dest):
+    os.makedirs(dest)
+    utils.download_file_from_cloud_storage(perf.GetGSLocation('*'),
+                                           dest,
+                                           concurrent=True,
+                                           flags=['-R'])
+
+
+def ParseJsonFromCloudStorage(filename, local_bucket):
+    abs_path = os.path.join(local_bucket, filename)
+    if not os.path.exists(abs_path):
+        return None
+    with open(abs_path, 'r') as f:
+        lines = f.readlines()
+        content = ''.join(lines)
+        try:
+            return json.loads(content)
+        except:
+            return None
+
+
+def run():
+    # Get the N most recent commits sorted by newest first.
+    top = utils.get_sha1_from_revision('origin/main')
+    bottom = utils.get_nth_sha1_from_revision(NUM_COMMITS - 1, 'origin/main')
+    commits = historic_run.enumerate_git_commits(top, bottom)
+    assert len(commits) == NUM_COMMITS
+
+    # Download all benchmark data from the cloud bucket to a temp folder.
+    with utils.TempDir() as temp:
+        local_bucket = os.path.join(temp, perf.BUCKET)
+        DownloadCloudBucket(local_bucket)
+
+        # Aggregate all the result.json files into a single benchmark_data.json file
+        # that has the same format as tools/perf/benchmark_data.json.
+        benchmark_data = []
+        for commit in commits:
+            benchmarks = {}
+            for app in APPS:
+                for target in TARGETS:
+                    filename = perf.GetArtifactLocation(app, target,
+                                                        commit.hash(),
+                                                        'result.json')
+                    app_benchmark_data = ParseJsonFromCloudStorage(
+                        filename, local_bucket)
+                    if app_benchmark_data:
+                        benchmarks[app] = app_benchmark_data
+            if benchmarks or benchmark_data:
+                benchmark_data.append({
+                    'author': commit.author_name(),
+                    'hash': commit.hash(),
+                    'submitted': commit.committer_timestamp(),
+                    'title': commit.title(),
+                    'benchmarks': benchmarks
+                })
+
+        # Trim data.
+        new_benchmark_data_len = len(benchmark_data)
+        while new_benchmark_data_len > 0:
+            candidate_len = new_benchmark_data_len - 1
+            if not benchmark_data[candidate_len]['benchmarks']:
+                new_benchmark_data_len = candidate_len
+            else:
+                break
+        benchmark_data = benchmark_data[0:new_benchmark_data_len]
+
+        # Serialize JSON to temp file.
+        benchmark_data_file = os.path.join(temp, 'benchmark_data.json')
+        with open(benchmark_data_file, 'w') as f:
+            json.dump(benchmark_data, f)
+
+        # Write output files to public bucket.
+        perf.ArchiveOutputFile(benchmark_data_file,
+                               'perf/benchmark_data.json',
+                               header='Cache-Control:no-store')
+        perf.ArchiveOutputFile(INDEX_HTML, 'perf/index.html')
+
+
+def main():
+    run()
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/tools/utils.py b/tools/utils.py
index 6faecc8..2b77ed5 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -56,6 +56,7 @@
 GRADLE_TASK_TESTBASE_WITH_APPLY_MAPPING_JAR = ':test:rewriteTestBaseForR8LibWithRelocatedDeps'
 GRADLE_TASK_TEST_DEPS_JAR = ':test:packageTestDeps'
 GRADLE_TASK_TEST_JAR = ':test:relocateTestsForR8LibWithRelocatedDeps'
+GRADLE_TASK_TEST_UNZIP_TESTBASE = ':test:unzipTestBase'
 
 R8 = 'r8'
 R8LIB = 'r8lib'
@@ -67,8 +68,10 @@
 R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
 R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar')
 R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')
-THREADING_MODULE_BLOCKING_JAR = os.path.join(LIBS, 'threading-module-blocking.jar')
-THREADING_MODULE_SINGLE_THREADED_JAR = os.path.join(LIBS, 'threading-module-single-threaded.jar')
+THREADING_MODULE_BLOCKING_JAR = os.path.join(LIBS,
+                                             'threading-module-blocking.jar')
+THREADING_MODULE_SINGLE_THREADED_JAR = os.path.join(
+    LIBS, 'threading-module-single-threaded.jar')
 R8_TESTS_JAR = os.path.join(LIBS, 'r8tests.jar')
 R8_TESTBASE_JAR = os.path.join(LIBS, 'r8test_base.jar')
 R8LIB_TESTBASE_JAR = os.path.join(LIBS, 'r8libtestbase-cf.jar')
@@ -81,7 +84,8 @@
 LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(
     CUSTOM_CONVERSION_DIR, 'library_desugar_conversions.jar')
 KEEPANNO_ANNOTATIONS_JAR = os.path.join(LIBS, 'keepanno-annotations.jar')
-KEEPANNO_ANNOTATIONS_DOC = os.path.join('d8_r8', 'keepanno', 'build', 'docs', 'javadoc')
+KEEPANNO_ANNOTATIONS_DOC = os.path.join('d8_r8', 'keepanno', 'build', 'docs',
+                                        'javadoc')
 
 DESUGAR_CONFIGURATION = os.path.join('src', 'library_desugar',
                                      'desugar_jdk_libs.json')
@@ -346,6 +350,14 @@
         subprocess.check_output(cmd)
 
 
+def get_nth_sha1_from_revision(n, revision):
+    result = subprocess.check_output([
+        'git', 'log', revision, f'--skip={n}', '--max-count=1',
+        '--pretty=format:%H'
+    ]).decode('utf-8')
+    return result.strip()
+
+
 def get_sha1(filename):
     sha1 = hashlib.sha1()
     with open(filename, 'rb') as f:
@@ -357,6 +369,13 @@
     return sha1.hexdigest()
 
 
+def get_sha1_from_revision(revision):
+    cmd = ['git', 'rev-parse', revision]
+    PrintCmd(cmd)
+    with ChangedWorkingDirectory(REPO_ROOT):
+        return subprocess.check_output(cmd).decode('utf-8').strip()
+
+
 def get_HEAD_branch():
     result = subprocess.check_output(
         ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode('utf-8')
@@ -364,20 +383,13 @@
 
 
 def get_HEAD_sha1():
-    return get_HEAD_sha1_for_checkout(REPO_ROOT)
+    return get_sha1_from_revision('HEAD')
 
 
 def get_HEAD_diff_stat():
     return subprocess.check_output(['git', 'diff', '--stat']).decode('utf-8')
 
 
-def get_HEAD_sha1_for_checkout(checkout):
-    cmd = ['git', 'rev-parse', 'HEAD']
-    PrintCmd(cmd)
-    with ChangedWorkingDirectory(checkout):
-        return subprocess.check_output(cmd).decode('utf-8').strip()
-
-
 def makedirs_if_needed(path):
     try:
         os.makedirs(path)
@@ -390,12 +402,15 @@
     return 'gsutil.py' if os.name != 'nt' else 'gsutil.py.bat'
 
 
-def upload_file_to_cloud_storage(source, destination):
-    cmd = [get_gsutil(), 'cp']
-    cmd += [source, destination]
+def upload_file_to_cloud_storage(source, destination, header=None):
+    cmd = [get_gsutil()]
+    if header:
+        cmd.extend(['-h', header])
+    cmd.extend(['cp', source, destination])
     PrintCmd(cmd)
     subprocess.check_call(cmd)
 
+
 def check_dir_args(source, destination):
     # We require that the dirname of the paths coincide, e.g., src/dirname and dst/dirname
     # The target is then stripped so the upload command will be: cp -R src/dirname dst/
@@ -406,10 +421,11 @@
             f'{source} and {destination}')
     if len(destination_parent.strip()) == 0:
         raise Exception(
-            'Attempt to upload directory to empty destination directory: '
-            + destination)
+            'Attempt to upload directory to empty destination directory: ' +
+            destination)
     return destination_parent
 
+
 def upload_directory_to_cloud_storage(source, destination, parallel=True):
     destination_parent = check_dir_args(source, destination)
     cmd = [get_gsutil()]
@@ -420,6 +436,7 @@
     PrintCmd(cmd)
     subprocess.check_call(cmd)
 
+
 def rsync_directory_to_cloud_storage(source, destination, parallel=True):
     check_dir_args(source, destination)
     cmd = [get_gsutil()]
@@ -430,6 +447,7 @@
     PrintCmd(cmd)
     subprocess.check_call(cmd)
 
+
 def delete_file_from_cloud_storage(destination):
     cmd = [get_gsutil(), 'rm', destination]
     PrintCmd(cmd)
@@ -460,8 +478,18 @@
     return subprocess.call(cmd) == 0
 
 
-def download_file_from_cloud_storage(source, destination, quiet=False):
-    cmd = [get_gsutil(), 'cp', source, destination]
+def download_file_from_cloud_storage(source,
+                                     destination,
+                                     concurrent=False,
+                                     flags=None,
+                                     quiet=False):
+    cmd = [get_gsutil()]
+    if concurrent:
+        cmd.append('-m')
+    cmd.append('cp')
+    if flags:
+        cmd.extend(flags)
+    cmd.extend([source, destination])
     PrintCmd(cmd, quiet=quiet)
     subprocess.check_call(cmd)