Merge commit 'fb2abd1e8fe4b3e8730ecdba55608123671587dc' into dev-release
diff --git a/build.gradle b/build.gradle
index e83113a..ce0bc7f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -136,15 +136,6 @@
             srcDirs = ['src/test/examplesJava17']
         }
     }
-    jdk11TimeTests {
-        java {
-            srcDirs = [
-                    'third_party/openjdk/jdk-11-test/java/time/tck',
-                    'third_party/openjdk/jdk-11-test/java/time/test'
-            ]
-            exclude '**/TestZoneTextPrinterParser.java'
-        }
-    }
     examplesTestNGRunner {
         java {
             srcDirs = ['src/test/testngrunner']
@@ -249,7 +240,6 @@
     implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
     implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
     implementation files('third_party/android_jar/api-database/api-database.jar')
-    jdk11TimeTestsCompile group: 'org.testng', name: 'testng', version: testngVersion
     examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
     testCompile sourceSets.examples.output
     testCompile "junit:junit:$junitVersion"
@@ -599,11 +589,6 @@
         JavaVersion.VERSION_1_9,
         false)
 setJdkCompilationWithCompatibility(
-        sourceSets.examplesJava11.compileJavaTaskName,
-        'jdk-11',
-        JavaVersion.VERSION_11,
-        false)
-setJdkCompilationWithCompatibility(
         sourceSets.examplesJava10.compileJavaTaskName,
         'jdk-11',
         JavaVersion.VERSION_1_10,
@@ -619,11 +604,6 @@
         JavaVersion.VERSION_11,
         false)
 setJdkCompilationWithCompatibility(
-        sourceSets.jdk11TimeTests.compileJavaTaskName,
-        'jdk-11',
-        JavaVersion.VERSION_11,
-        false)
-setJdkCompilationWithCompatibility(
         sourceSets.examplesJava17.compileJavaTaskName,
         'jdk-17',
         JavaVersion.VERSION_17,
@@ -648,6 +628,19 @@
     classpath = sourceSets.main.compileClasspath
 }
 
+task provideJdk11TestsDependencies(type: org.gradle.api.tasks.Copy) {
+    from sourceSets.examplesTestNGRunner.compileClasspath
+    include "**/**.jar"
+    into file("$buildDir/test/jdk11Tests")
+}
+
+task compileTestNGRunner (type: JavaCompile) {
+    dependsOn provideJdk11TestsDependencies
+    destinationDir = file("$buildDir/classes/java/examplesTestNGRunner")
+    source = sourceSets.examplesTestNGRunner.allSource
+    classpath = sourceSets.examplesTestNGRunner.compileClasspath
+}
+
 if (!project.hasProperty('without_error_prone') &&
         // Don't enable error prone on Java 8 as the plugin setup does not support it.
         !org.gradle.internal.jvm.Jvm.current().javaVersion.java8) {
@@ -1634,28 +1627,6 @@
     })
 }
 
-task provideJdk11TestsDependencies(type: org.gradle.api.tasks.Copy) {
-    from sourceSets.jdk11TimeTests.compileClasspath
-    include "**/**.jar"
-    into file("build/test/jdk11Tests")
-}
-
-task buildJdk11TimeTestsJar {
-    def exampleOutputDir = file("build/test/jdk11Tests");
-    def jarName = "jdk11TimeTests.jar"
-    dependsOn "jar_jdk11TimeTests"
-    dependsOn provideJdk11TestsDependencies
-    task "jar_jdk11TimeTests"(type: Jar) {
-        archiveName = jarName
-        destinationDir = exampleOutputDir
-        from sourceSets.examplesTestNGRunner.output
-        include "**.class"
-        from sourceSets.jdk11TimeTests.output
-        include "**.class"
-        include "**/**.class"
-    }
-}
-
 task buildKotlinR8TestResources {
     def examplesDir = file("src/test/kotlinR8TestResources")
     examplesDir.eachDir { dir ->
@@ -1793,6 +1764,10 @@
     } else {
       // On normal work machines this seems to give the best test execution time (without freezing)
       maxParallelForks = processors.intdiv(3) ?: 1
+      // On low cpu count machines (bots) we under subscribe, so increase the count.
+      if (processors == 8) {
+        maxParallelForks = 4
+      }
     }
     println("NOTE: Max parallel forks " + maxParallelForks)
 
@@ -1900,10 +1875,6 @@
     def err = new StringBuffer()
     def command = "python tools/retrace.py --quiet"
     def header = "RETRACED STACKTRACE";
-    if (System.getenv('BUILDBOT_BUILDERNAME') != null
-            && !System.getenv('BUILDBOT_BUILDERNAME').endsWith("_release")) {
-        header += ": (${command} --commit_hash ${System.getenv('BUILDBOT_REVISION')})";
-    }
     out.append("\n--------------------------------------\n")
     out.append("${header}\n")
     out.append("--------------------------------------\n")
@@ -2411,7 +2382,7 @@
         dependsOn buildDebugInfoExamplesDex
         dependsOn buildPreNJdwpTestsJar
         dependsOn buildPreNJdwpTestsDex
-        dependsOn buildJdk11TimeTestsJar
+        dependsOn compileTestNGRunner
         dependsOn provideArtFrameworksDependencies
     } else {
         logger.lifecycle("WARNING: Testing in not supported on your platform. Testing is only fully supported on " +
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
deleted file mode 100644
index ae6487f..0000000
--- a/infra/config/global/cr-buildbucket.cfg
+++ /dev/null
@@ -1,525 +0,0 @@
-# Defines buckets on cr-buildbucket.appspot.com, used by to schedule builds
-# on buildbot. In particular, CQ uses some of these buckets to schedule tryjobs.
-#
-# See http://luci-config.appspot.com/schemas/projects:buildbucket.cfg for
-# schema of this file and documentation.
-#
-# Please keep this list sorted by bucket name.
-
-builder_mixins {
-  name: "linux"
-  dimensions: "os:Ubuntu-16.04"
-}
-
-builder_mixins {
-  name: "win"
-  dimensions: "os:Windows-10"
-}
-
-builder_mixins {
-  name: "normal"
-  dimensions: "normal:true"
-}
-
-builder_mixins {
-  name: "jctf"
-  dimensions: "jctf:true"
-}
-
-builder_mixins {
-  name: "internal"
-  dimensions: "internal:true"
-  dimensions: "cores:2"
-}
-
-builder_mixins {
-  name: "mac"
-  dimensions: "os:Mac-10.13"
-  dimensions: "cores:"  # Macs can be 4 or 8 cores.
-}
-
-builder_mixins {
-  name: "build_limited_scripts_slave recipe"
-  recipe {
-    cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-    cipd_version: "refs/heads/master"
-  }
-}
-
-builder_mixins {
-  name: "fast_bot"
-  execution_timeout_secs: 5400 # 1.5 hours
-}
-
-acl_sets {
-  name: "ci"
-  acls {
-    role: READER
-    group: "all"
-  }
-  acls {
-    role: SCHEDULER
-    identity: "luci-scheduler@appspot.gserviceaccount.com"
-  }
-  acls {
-    role: SCHEDULER
-    group: "project-r8-committers"
-  }
-}
-
-acl_sets {
-  name: "try"
-  acls {
-    role: READER
-    group: "project-r8-readers"
-  }
-  acls {
-    role: WRITER
-    group: "project-r8-admins"
-  }
-  acls {
-    role: SCHEDULER
-    group: "service-account-cq"
-  }
-  acls {
-    role: SCHEDULER
-    group: "project-r8-tryjob-access"
-  }
-}
-
-buckets {
-  name: "luci.r8.ci"
-  acl_sets: "ci"
-  swarming {
-    hostname: "chrome-swarming.appspot.com"
-    builder_defaults {
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "pool:luci.r8.ci"
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      execution_timeout_secs: 21600  # 6h
-      expiration_secs: 126000 # 35h, execution_timeout_secs + expiration_secs must be <=47h
-      swarming_tags: "vpython:native-python-wrapper"
-      build_numbers: YES
-      recipe {
-        properties: "builder_group:internal.client.r8"
-        name: "rex"
-      }
-      mixins: "build_limited_scripts_slave recipe"
-    }
-
-    builders {
-      name: "archive"
-      priority: 25
-      mixins: "linux"
-      execution_timeout_secs: 1800  # 1/2h
-      recipe {
-        properties: "archive:True"
-      }
-    }
-    builders {
-      name: "archive_release"
-      priority: 25
-      mixins: "linux"
-      execution_timeout_secs: 1800  # 1/2h
-      recipe {
-        properties: "archive:True"
-      }
-
-    }
-    # This builder is only triggered manually to build and archive maven
-    # artifacts for the desugared library.
-    builders {
-      name: "archive_lib_desugar"
-      priority: 25
-      mixins: "linux"
-      execution_timeout_secs: 3600  # 1h
-      recipe {
-        properties: "archive:True"
-        properties: "sdk_desugar:True"
-      }
-    }
-    builders {
-      name: "desugared_library_head"
-      execution_timeout_secs: 43200  # 12h
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--no_internal\", \"--desugared-library\", \"HEAD\"]"
-      }
-    }
-    builders {
-      name: "desugared_library_jdk11_head"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--no_internal\", \"--desugared-library\", \"HEAD\", \"--desugared-library-configuration\", \"jdk11\"]"
-      }
-    }
-    builders {
-      name: "linux-dex-default"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=dex-default\", \"--tool=r8\", \"--no_internal\", \"--all_tests\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-none"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=none\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-jdk8"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=jdk8\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-jdk9"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=jdk9\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-jdk11"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=jdk11\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-dex-default_release"
-      mixins: "normal"
-      mixins: "linux"
-      recipe {
-        properties_j: "test_options:[\"--runtimes=dex-default\", \"--tool=r8\", \"--no_internal\", \"--all_tests\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-none_release"
-      mixins: "normal"
-      mixins: "linux"
-      recipe {
-        properties_j: "test_options:[\"--runtimes=none\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-jdk8_release"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=jdk8\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-jdk9_release"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=jdk9\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-jdk11_release"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 26
-      recipe {
-        properties_j: "test_options:[\"--runtimes=jdk11\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
-      }
-    }
-    builders {
-      name: "linux-android-4.0.4"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:4.0.4"
-      }
-    }
-    builders {
-      name: "linux-android-4.0.4_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:4.0.4"
-      }
-    }
-    builders {
-      name: "linux-android-4.4.4"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:4.4.4"
-      }
-    }
-    builders {
-      name: "linux-android-4.4.4_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:4.4.4"
-      }
-    }
-    builders {
-      name: "linux-android-5.1.1"
-      mixins: "normal"
-      mixins: "linux"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:5.1.1"
-      }
-    }
-    builders {
-      name: "linux-android-5.1.1_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:5.1.1"
-      }
-    }
-    builders {
-      name: "linux-android-6.0.1"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:6.0.1"
-      }
-    }
-    builders {
-      name: "linux-android-6.0.1_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:6.0.1"
-      }
-    }
-    builders {
-      name: "linux-android-7.0.0"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:7.0.0"
-      }
-    }
-    builders {
-      name: "linux-android-7.0.0_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:7.0.0"
-      }
-    }
-    builders {
-      name: "linux-android-8.1.0"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:8.1.0"
-      }
-    }
-    builders {
-      name: "linux-android-8.1.0_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:8.1.0"
-      }
-    }
-    builders {
-      name: "linux-android-9.0.0"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:9.0.0"
-      }
-    }
-    builders {
-      name: "linux-android-9.0.0_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:9.0.0"
-      }
-    }
-    builders {
-      name: "linux-android-10.0.0"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:10.0.0"
-      }
-    }
-    builders {
-      name: "linux-android-10.0.0_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:10.0.0"
-      }
-    }
-    builders {
-      name: "linux-android-12.0.0"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:12.0.0"
-      }
-    }
-    builders {
-      name: "linux-android-12.0.0_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties: "tool:r8"
-        properties: "dex_vm:12.0.0"
-      }
-    }
-    builders {
-      name: "linux-internal"
-      mixins: "linux"
-      mixins: "internal"
-      execution_timeout_secs: 43200  # 12h
-      recipe {
-        properties: "internal:True"
-      }
-    }
-    builders {
-      name: "linux-internal_release"
-      mixins: "linux"
-      mixins: "internal"
-      execution_timeout_secs: 43200  # 12h
-      recipe {
-        properties: "internal:True"
-      }
-    }
-    builders {
-      name: "linux-run-on-app-dump"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties_j: "test_options:[\"--bot\"]"
-        properties: "test_wrapper:tools/run_on_app_dump.py"
-      }
-    }
-    builders {
-      name: "linux-run-on-app-dump_release"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties_j: "test_options:[\"--bot\"]"
-        properties: "test_wrapper:tools/run_on_app_dump.py"
-      }
-    }
-    builders {
-      name: "linux-jctf"
-      mixins: "linux"
-      mixins: "jctf"
-      execution_timeout_secs: 43200  # 12h
-      recipe {
-        properties: "tool:d8"
-        properties: "dex_vm:all"
-        properties: "only_jctf:True"
-      }
-    }
-    builders {
-      name: "linux-jctf_release"
-      mixins: "linux"
-      mixins: "jctf"
-      execution_timeout_secs: 43200  # 12h
-      recipe {
-        properties: "tool:d8"
-        properties: "dex_vm:all"
-        properties: "only_jctf:True"
-      }
-    }
-    builders {
-      name: "r8cf-linux-jctf"
-      mixins: "linux"
-      mixins: "jctf"
-      execution_timeout_secs: 43200  # 12h
-      recipe {
-        properties: "tool:r8cf"
-        properties: "dex_vm:all"
-        properties: "only_jctf:True"
-      }
-    }
-    builders {
-      name: "r8cf-linux-jctf_release"
-      mixins: "linux"
-      mixins: "jctf"
-      execution_timeout_secs: 43200  # 12h
-      recipe {
-        properties: "tool:r8cf"
-        properties: "dex_vm:all"
-        properties: "only_jctf:True"
-      }
-    }
-    builders {
-      name: "linux-kotlin-dev"
-      mixins: "linux"
-      mixins: "normal"
-      recipe {
-        properties_j: "test_options:[\"--no_internal\", \"--runtimes=dex-default:jdk11\", \"--kotlin-compiler-dev\", \"--one_line_per_test\", \"--archive_failures\", \"*kotlin*\"]"
-      }
-    }
-    builders {
-      name: "windows"
-      mixins: "win"
-      recipe {
-        properties: "tool:r8"
-      }
-    }
-    builders {
-      name: "windows_release"
-      mixins: "win"
-      recipe {
-        properties: "tool:r8"
-      }
-    }
-    builders {
-      name: "kotlin-builder"
-      mixins: "linux"
-      mixins: "normal"
-      priority: 27
-      recipe {
-        properties_j: "test_options:[\"--not_used\"]"
-        properties: "test_wrapper:google-scripts/build.py"
-        properties: "kotlin_repo:True"
-      }
-    }
-  }
-}
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 3d645b7..6dd5ceb 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -30,36 +30,19 @@
         name: "rex"
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
-        properties_j: "archive:\"true\""
         properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"not_used_but_evaluates_to_python_true\"]"
+        properties_j: "test_wrapper:\"tools/archive.py\""
       }
       priority: 25
       execution_timeout_secs: 1800
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    }
-    builders {
-      name: "archive_lib_desugar"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      recipe {
-        name: "rex"
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        properties_j: "archive:\"true\""
-        properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "sdk_desugar:\"true\""
+      experiments {
+        key: "luci.use_realms"
+        value: 100
       }
-      priority: 25
-      execution_timeout_secs: 3600
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     }
     builders {
       name: "archive_release"
@@ -73,17 +56,22 @@
         name: "rex"
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
-        properties_j: "archive:\"true\""
         properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"not_used_but_evaluates_to_python_true\"]"
+        properties_j: "test_wrapper:\"tools/archive.py\""
       }
       priority: 25
       execution_timeout_secs: 1800
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
-      name: "desugared_library_head"
+      name: "desugared_library-head"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -102,9 +90,13 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
-      name: "desugared_library_jdk11_head"
+      name: "desugared_library-jdk11_head"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -123,6 +115,140 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+    }
+    builders {
+      name: "lib_desugar-archive"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      recipe {
+        name: "rex"
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"not_used_but_evaluates_to_python_true\"]"
+        properties_j: "test_wrapper:\"tools/archive_desugar_jdk_libs.py\""
+      }
+      priority: 25
+      execution_timeout_secs: 3600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+    }
+    builders {
+      name: "linux-android-10.0.0"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      recipe {
+        name: "rex"
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"--dex_vm=10.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
+      }
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+    }
+    builders {
+      name: "linux-android-10.0.0_release"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      recipe {
+        name: "rex"
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"--dex_vm=10.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
+      }
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+    }
+    builders {
+      name: "linux-android-12.0.0"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      recipe {
+        name: "rex"
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"--dex_vm=12.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
+      }
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+    }
+    builders {
+      name: "linux-android-12.0.0_release"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      recipe {
+        name: "rex"
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        properties_j: "builder_group:\"internal.client.r8\""
+        properties_j: "test_options:[\"--dex_vm=12.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
+      }
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-4.0.4"
@@ -145,6 +271,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-4.0.4_release"
@@ -167,6 +297,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-4.4.4"
@@ -189,6 +323,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-4.4.4_release"
@@ -211,6 +349,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-5.1.1"
@@ -233,6 +375,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-5.1.1_release"
@@ -255,6 +401,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-6.0.1"
@@ -277,6 +427,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-6.0.1_release"
@@ -299,6 +453,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-7.0.0"
@@ -321,6 +479,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-android-7.0.0_release"
@@ -343,97 +505,13 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    }
-    builders {
-      name: "linux-android=10.0.0"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "normal:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      recipe {
-        name: "rex"
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "test_options:[\"--dex_vm=10.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
       }
-      priority: 26
-      execution_timeout_secs: 21600
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     }
     builders {
-      name: "linux-android=10.0.0_release"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "normal:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      recipe {
-        name: "rex"
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "test_options:[\"--dex_vm=10.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
-      }
-      priority: 26
-      execution_timeout_secs: 21600
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    }
-    builders {
-      name: "linux-android=12.0.0"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "normal:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      recipe {
-        name: "rex"
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "test_options:[\"--dex_vm=12.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
-      }
-      priority: 26
-      execution_timeout_secs: 21600
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    }
-    builders {
-      name: "linux-android=12.0.0_release"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "normal:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      recipe {
-        name: "rex"
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "test_options:[\"--dex_vm=12.0.0\",\"--all_tests\",\"--tool=r8\",\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\"]"
-      }
-      priority: 26
-      execution_timeout_secs: 21600
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-    }
-    builders {
-      name: "linux-android=8.1.0"
+      name: "linux-android-8.1.0"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -453,9 +531,13 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
-      name: "linux-android=8.1.0_release"
+      name: "linux-android-8.1.0_release"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -475,9 +557,13 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
-      name: "linux-android=9.0.0"
+      name: "linux-android-9.0.0"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -497,9 +583,13 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
-      name: "linux-android=9.0.0_release"
+      name: "linux-android-9.0.0_release"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -519,6 +609,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-d8_jctf"
@@ -534,15 +628,17 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "dex_vm:\"all\""
-        properties_j: "only_jctf:\"true\""
-        properties_j: "tool:\"d8\""
+        properties_j: "test_options:[\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\",\"--dex_vm=all\",\"--tool=d8\",\"--only_jctf\"]"
       }
       priority: 26
       execution_timeout_secs: 43200
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-d8_jctf_release"
@@ -558,15 +654,17 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "dex_vm:\"all\""
-        properties_j: "only_jctf:\"true\""
-        properties_j: "tool:\"d8\""
+        properties_j: "test_options:[\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\",\"--dex_vm=all\",\"--tool=d8\",\"--only_jctf\"]"
       }
       priority: 26
       execution_timeout_secs: 43200
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-dex_default"
@@ -589,6 +687,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-dex_default_release"
@@ -611,6 +713,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-internal"
@@ -626,13 +732,18 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "internal:\"true\""
+        properties_j: "test_options:[\"--bot\"]"
+        properties_j: "test_wrapper:\"tools/internal_test.py\""
       }
       priority: 25
       execution_timeout_secs: 43200
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-internal_release"
@@ -648,13 +759,18 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "internal:\"true\""
+        properties_j: "test_options:[\"--bot\"]"
+        properties_j: "test_wrapper:\"tools/internal_test.py\""
       }
       priority: 25
       execution_timeout_secs: 43200
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-jdk11"
@@ -677,6 +793,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-jdk11_release"
@@ -699,6 +819,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-jdk8"
@@ -721,6 +845,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-jdk8_release"
@@ -743,6 +871,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-jdk9"
@@ -765,6 +897,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-jdk9_release"
@@ -787,9 +923,13 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
-      name: "linux-kotlin-dev"
+      name: "linux-kotlin_dev"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
@@ -808,6 +948,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-none"
@@ -830,6 +974,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-none_release"
@@ -852,6 +1000,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-r8cf_jctf"
@@ -867,15 +1019,17 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "dex_vm:\"all\""
-        properties_j: "only_jctf:\"true\""
-        properties_j: "tool:\"r8cf\""
+        properties_j: "test_options:[\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\",\"--dex_vm=all\",\"--tool=r8cf\",\"--only_jctf\"]"
       }
       priority: 26
       execution_timeout_secs: 43200
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-r8cf_jctf_release"
@@ -891,15 +1045,17 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "dex_vm:\"all\""
-        properties_j: "only_jctf:\"true\""
-        properties_j: "tool:\"r8cf\""
+        properties_j: "test_options:[\"--no_internal\",\"--one_line_per_test\",\"--archive_failures\",\"--dex_vm=all\",\"--tool=r8cf\",\"--only_jctf\"]"
       }
       priority: 26
       execution_timeout_secs: 43200
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-run-on-app-dump"
@@ -922,6 +1078,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "linux-run-on-app-dump_release"
@@ -944,6 +1104,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "windows"
@@ -951,7 +1115,7 @@
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:windows-10"
+      dimensions: "os:Windows-10"
       dimensions: "pool:luci.r8.ci"
       recipe {
         name: "rex"
@@ -965,6 +1129,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
     builders {
       name: "windows_release"
@@ -972,7 +1140,7 @@
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:windows-10"
+      dimensions: "os:Windows-10"
       dimensions: "pool:luci.r8.ci"
       recipe {
         name: "rex"
@@ -986,6 +1154,10 @@
       expiration_secs: 126000
       build_numbers: YES
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
     }
   }
 }
diff --git a/infra/config/global/generated/luci-logdog.cfg b/infra/config/global/generated/luci-logdog.cfg
new file mode 100644
index 0000000..57dada0
--- /dev/null
+++ b/infra/config/global/generated/luci-logdog.cfg
@@ -0,0 +1,9 @@
+# Auto-generated by lucicfg.
+# Do not modify manually.
+#
+# For the schema of this file, see ProjectConfig message:
+#   https://luci-config.appspot.com/schemas/projects:luci-logdog.cfg
+
+reader_auth_groups: "all"
+writer_auth_groups: "luci-logdog-r8-writers"
+archive_gs_bucket: "logdog-r8-archive"
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index 736c8cc..a48c9aa 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -8,212 +8,82 @@
   id: "main"
   name: "R8 Main Console"
   repo_url: "https://r8.googlesource.com/r8"
-  refs: "regexp:regexp:refs/heads/.*"
+  refs: "regexp:refs/heads/.*"
   manifest_name: "REVISION"
   builders {
-    name: "buildbucket/luci.r8.ci/linux-d8_jctf"
-    category: "R8"
-    short_name: "d8_jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-r8cf_jctf"
-    category: "R8"
-    short_name: "r8cf_jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-d8_jctf_release"
-    category: "R8 release"
-    short_name: "d8_jctf_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-r8cf_jctf_release"
-    category: "R8 release"
-    short_name: "r8cf_jctf_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump"
-    category: "R8"
-    short_name: "dump"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump_release"
-    category: "R8 release"
-    short_name: "dump_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/desugared_library_head"
-    category: "R8"
-    short_name: "desugared_library_head"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/desugared_library_jdk11_head"
-    category: "R8"
-    short_name: "desugared_library_jdk11_head"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/archive"
-    category: "R8"
+    category: "archive"
     short_name: "archive"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/archive_release"
-    category: "R8 release"
-    short_name: "archive_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/archive_lib_desugar"
-    category: "R8"
-    short_name: "archive_lib_desugar"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-internal"
-    category: "R8"
-    short_name: "internal"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-internal_release"
-    category: "R8 release"
-    short_name: "internal_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-dex_default"
     category: "R8"
     short_name: "dex_default"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-dex_default_release"
-    category: "R8 release"
-    short_name: "dex_default_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-none"
     category: "R8"
     short_name: "none"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-none_release"
-    category: "R8 release"
-    short_name: "none_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-jdk8"
     category: "R8"
     short_name: "jdk8"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk8_release"
-    category: "R8 release"
-    short_name: "jdk8_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-jdk9"
     category: "R8"
     short_name: "jdk9"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk9_release"
-    category: "R8 release"
-    short_name: "jdk9_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-jdk11"
     category: "R8"
     short_name: "jdk11"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk11_release"
-    category: "R8 release"
-    short_name: "jdk11_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0.4"
     category: "R8"
     short_name: "4.0.4"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android-4.0.4_release"
-    category: "R8 release"
-    short_name: "4.0.4_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.4.4"
     category: "R8"
     short_name: "4.4.4"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android-4.4.4_release"
-    category: "R8 release"
-    short_name: "4.4.4_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-android-5.1.1"
     category: "R8"
     short_name: "5.1.1"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android-5.1.1_release"
-    category: "R8 release"
-    short_name: "5.1.1_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-android-6.0.1"
     category: "R8"
     short_name: "6.0.1"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android-6.0.1_release"
-    category: "R8 release"
-    short_name: "6.0.1_release"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/linux-android-7.0.0"
     category: "R8"
     short_name: "7.0.0"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android-7.0.0_release"
-    category: "R8 release"
-    short_name: "7.0.0_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android=8.1.0"
+    name: "buildbucket/luci.r8.ci/linux-android-8.1.0"
     category: "R8"
-    short_name: "android=8.1.0"
+    short_name: "8.1.0"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android=8.1.0_release"
-    category: "R8 release"
-    short_name: "android=8.1.0_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android=9.0.0"
+    name: "buildbucket/luci.r8.ci/linux-android-9.0.0"
     category: "R8"
-    short_name: "android=9.0.0"
+    short_name: "9.0.0"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android=9.0.0_release"
-    category: "R8 release"
-    short_name: "android=9.0.0_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android=10.0.0"
+    name: "buildbucket/luci.r8.ci/linux-android-10.0.0"
     category: "R8"
-    short_name: "android=10.0.0"
+    short_name: "10.0.0"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-android=10.0.0_release"
-    category: "R8 release"
-    short_name: "android=10.0.0_release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android=12.0.0"
+    name: "buildbucket/luci.r8.ci/linux-android-12.0.0"
     category: "R8"
-    short_name: "android=12.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android=12.0.0_release"
-    category: "R8 release"
-    short_name: "android=12.0.0_release"
+    short_name: "12.0.0"
   }
   builders {
     name: "buildbucket/luci.r8.ci/windows"
@@ -221,13 +91,143 @@
     short_name: "windows"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/windows_release"
-    category: "R8 release"
-    short_name: "windows_release"
+    name: "buildbucket/luci.r8.ci/linux-internal"
+    category: "R8"
+    short_name: "internal"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-kotlin-dev"
+    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump"
     category: "R8"
-    short_name: "dev"
+    short_name: "dump"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-kotlin_dev"
+    category: "R8"
+    short_name: "kotlin_dev"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-d8_jctf"
+    category: "jctf"
+    short_name: "d8_jctf"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-r8cf_jctf"
+    category: "jctf"
+    short_name: "r8cf_jctf"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/lib_desugar-archive"
+    category: "library_desugar"
+    short_name: "archive"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/desugared_library-head"
+    category: "library_desugar"
+    short_name: "head"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/desugared_library-jdk11_head"
+    category: "library_desugar"
+    short_name: "jdk11_head"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/archive_release"
+    category: "Release|archive"
+    short_name: "archive"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-dex_default_release"
+    category: "Release|R8"
+    short_name: "dex_default"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-none_release"
+    category: "Release|R8"
+    short_name: "none"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk8_release"
+    category: "Release|R8"
+    short_name: "jdk8"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk9_release"
+    category: "Release|R8"
+    short_name: "jdk9"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk11_release"
+    category: "Release|R8"
+    short_name: "jdk11"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-4.0.4_release"
+    category: "Release|R8"
+    short_name: "4.0.4"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-4.4.4_release"
+    category: "Release|R8"
+    short_name: "4.4.4"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-5.1.1_release"
+    category: "Release|R8"
+    short_name: "5.1.1"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-6.0.1_release"
+    category: "Release|R8"
+    short_name: "6.0.1"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-7.0.0_release"
+    category: "Release|R8"
+    short_name: "7.0.0"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-8.1.0_release"
+    category: "Release|R8"
+    short_name: "8.1.0"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-9.0.0_release"
+    category: "Release|R8"
+    short_name: "9.0.0"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-10.0.0_release"
+    category: "Release|R8"
+    short_name: "10.0.0"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-android-12.0.0_release"
+    category: "Release|R8"
+    short_name: "12.0.0"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/windows_release"
+    category: "Release|R8"
+    short_name: "windows"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-internal_release"
+    category: "Release|R8"
+    short_name: "internal"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump_release"
+    category: "Release|R8"
+    short_name: "dump"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-d8_jctf_release"
+    category: "Release|jctf"
+    short_name: "d8_jctf"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-r8cf_jctf_release"
+    category: "Release|jctf"
+    short_name: "r8cf_jctf"
   }
 }
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg
index 8228f3e..c6af3c9 100644
--- a/infra/config/global/generated/luci-notify.cfg
+++ b/infra/config/global/generated/luci-notify.cfg
@@ -36,7 +36,7 @@
   }
   builders {
     bucket: "ci"
-    name: "desugared_library_head"
+    name: "desugared_library-head"
     repository: "https://r8.googlesource.com/r8"
   }
 }
@@ -48,7 +48,55 @@
   }
   builders {
     bucket: "ci"
-    name: "desugared_library_jdk11_head"
+    name: "desugared_library-jdk11_head"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-android-10.0.0"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-android-10.0.0_release"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-android-12.0.0"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-android-12.0.0_release"
     repository: "https://r8.googlesource.com/r8"
   }
 }
@@ -180,7 +228,7 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-android=10.0.0"
+    name: "linux-android-8.1.0"
     repository: "https://r8.googlesource.com/r8"
   }
 }
@@ -192,7 +240,7 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-android=10.0.0_release"
+    name: "linux-android-8.1.0_release"
     repository: "https://r8.googlesource.com/r8"
   }
 }
@@ -204,7 +252,7 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-android=12.0.0"
+    name: "linux-android-9.0.0"
     repository: "https://r8.googlesource.com/r8"
   }
 }
@@ -216,55 +264,7 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-android=12.0.0_release"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
-    name: "linux-android=8.1.0"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
-    name: "linux-android=8.1.0_release"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
-    name: "linux-android=9.0.0"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
-    name: "linux-android=9.0.0_release"
+    name: "linux-android-9.0.0_release"
     repository: "https://r8.googlesource.com/r8"
   }
 }
@@ -420,7 +420,7 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-kotlin-dev"
+    name: "linux-kotlin_dev"
     repository: "https://r8.googlesource.com/r8"
   }
 }
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index 46abc44..e9b307f 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -6,6 +6,7 @@
 
 job {
   id: "archive"
+  realm: "ci"
   acl_sets: "ci"
   triggering_policy {
     kind: GREEDY_BATCHING
@@ -19,21 +20,8 @@
   }
 }
 job {
-  id: "archive_lib_desugar"
-  acl_sets: "ci"
-  triggering_policy {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 3
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "archive_lib_desugar"
-  }
-}
-job {
   id: "archive_release"
+  realm: "ci"
   acl_sets: "ci"
   triggering_policy {
     kind: GREEDY_BATCHING
@@ -47,26 +35,112 @@
   }
 }
 job {
-  id: "desugared_library_head"
+  id: "desugared_library-head"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
-    builder: "desugared_library_head"
+    builder: "desugared_library-head"
   }
 }
 job {
-  id: "desugared_library_jdk11_head"
+  id: "desugared_library-jdk11_head"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
-    builder: "desugared_library_jdk11_head"
+    builder: "desugared_library-jdk11_head"
+  }
+}
+job {
+  id: "lib_desugar-archive"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 3
+    max_batch_size: 1
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "lib_desugar-archive"
+  }
+}
+job {
+  id: "linux-android-10.0.0"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-10.0.0"
+  }
+}
+job {
+  id: "linux-android-10.0.0_release"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-10.0.0_release"
+  }
+}
+job {
+  id: "linux-android-12.0.0"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-12.0.0"
+  }
+}
+job {
+  id: "linux-android-12.0.0_release"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-12.0.0_release"
   }
 }
 job {
   id: "linux-android-4.0.4"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -75,7 +149,12 @@
 }
 job {
   id: "linux-android-4.0.4_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -84,7 +163,12 @@
 }
 job {
   id: "linux-android-4.4.4"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -93,7 +177,12 @@
 }
 job {
   id: "linux-android-4.4.4_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -102,7 +191,12 @@
 }
 job {
   id: "linux-android-5.1.1"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -111,7 +205,12 @@
 }
 job {
   id: "linux-android-5.1.1_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -120,7 +219,12 @@
 }
 job {
   id: "linux-android-6.0.1"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -129,7 +233,12 @@
 }
 job {
   id: "linux-android-6.0.1_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -138,7 +247,12 @@
 }
 job {
   id: "linux-android-7.0.0"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -147,7 +261,12 @@
 }
 job {
   id: "linux-android-7.0.0_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -155,80 +274,69 @@
   }
 }
 job {
-  id: "linux-android=10.0.0"
+  id: "linux-android-8.1.0"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
-    builder: "linux-android=10.0.0"
+    builder: "linux-android-8.1.0"
   }
 }
 job {
-  id: "linux-android=10.0.0_release"
+  id: "linux-android-8.1.0_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
-    builder: "linux-android=10.0.0_release"
+    builder: "linux-android-8.1.0_release"
   }
 }
 job {
-  id: "linux-android=12.0.0"
+  id: "linux-android-9.0.0"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
-    builder: "linux-android=12.0.0"
+    builder: "linux-android-9.0.0"
   }
 }
 job {
-  id: "linux-android=12.0.0_release"
+  id: "linux-android-9.0.0_release"
+  realm: "ci"
   acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android=12.0.0_release"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
   }
-}
-job {
-  id: "linux-android=8.1.0"
-  acl_sets: "ci"
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
-    builder: "linux-android=8.1.0"
-  }
-}
-job {
-  id: "linux-android=8.1.0_release"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android=8.1.0_release"
-  }
-}
-job {
-  id: "linux-android=9.0.0"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android=9.0.0"
-  }
-}
-job {
-  id: "linux-android=9.0.0_release"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android=9.0.0_release"
+    builder: "linux-android-9.0.0_release"
   }
 }
 job {
   id: "linux-d8_jctf"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -237,7 +345,12 @@
 }
 job {
   id: "linux-d8_jctf_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -246,7 +359,12 @@
 }
 job {
   id: "linux-dex_default"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -255,7 +373,12 @@
 }
 job {
   id: "linux-dex_default_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -264,11 +387,11 @@
 }
 job {
   id: "linux-internal"
+  realm: "ci"
   acl_sets: "ci"
   triggering_policy {
     kind: GREEDY_BATCHING
     max_concurrent_invocations: 1
-    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
@@ -278,11 +401,11 @@
 }
 job {
   id: "linux-internal_release"
+  realm: "ci"
   acl_sets: "ci"
   triggering_policy {
     kind: GREEDY_BATCHING
     max_concurrent_invocations: 1
-    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
@@ -292,7 +415,12 @@
 }
 job {
   id: "linux-jdk11"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -301,7 +429,12 @@
 }
 job {
   id: "linux-jdk11_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -310,7 +443,12 @@
 }
 job {
   id: "linux-jdk8"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -319,7 +457,12 @@
 }
 job {
   id: "linux-jdk8_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -328,7 +471,12 @@
 }
 job {
   id: "linux-jdk9"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -337,7 +485,12 @@
 }
 job {
   id: "linux-jdk9_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -345,17 +498,27 @@
   }
 }
 job {
-  id: "linux-kotlin-dev"
+  id: "linux-kotlin_dev"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
-    builder: "linux-kotlin-dev"
+    builder: "linux-kotlin_dev"
   }
 }
 job {
   id: "linux-none"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -364,7 +527,12 @@
 }
 job {
   id: "linux-none_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -373,7 +541,12 @@
 }
 job {
   id: "linux-r8cf_jctf"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -382,7 +555,12 @@
 }
 job {
   id: "linux-r8cf_jctf_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -391,7 +569,12 @@
 }
 job {
   id: "linux-run-on-app-dump"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -400,7 +583,12 @@
 }
 job {
   id: "linux-run-on-app-dump_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -409,7 +597,12 @@
 }
 job {
   id: "windows"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -418,7 +611,12 @@
 }
 job {
   id: "windows_release"
+  realm: "ci"
   acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -427,17 +625,18 @@
 }
 trigger {
   id: "branch-gitiles-trigger"
+  realm: "ci"
   acl_sets: "ci"
   triggers: "archive_release"
+  triggers: "linux-android-10.0.0_release"
+  triggers: "linux-android-12.0.0_release"
   triggers: "linux-android-4.0.4_release"
   triggers: "linux-android-4.4.4_release"
   triggers: "linux-android-5.1.1_release"
   triggers: "linux-android-6.0.1_release"
   triggers: "linux-android-7.0.0_release"
-  triggers: "linux-android=10.0.0_release"
-  triggers: "linux-android=12.0.0_release"
-  triggers: "linux-android=8.1.0_release"
-  triggers: "linux-android=9.0.0_release"
+  triggers: "linux-android-8.1.0_release"
+  triggers: "linux-android-9.0.0_release"
   triggers: "linux-d8_jctf_release"
   triggers: "linux-dex_default_release"
   triggers: "linux-internal_release"
@@ -450,32 +649,33 @@
   triggers: "windows_release"
   gitiles {
     repo: "https://r8.googlesource.com/r8"
-    refs: "regexp:regexp:refs/heads/(?:d8-)?[0-9]+\\.[0-9]+(\\.[0-9]+)?"
+    refs: "regexp:refs/heads/(?:d8-)?[0-9]+\\.[0-9]+(\\.[0-9]+)?"
     path_regexps: "src/main/java/com/android/tools/r8/Version.java"
   }
 }
 trigger {
   id: "main-gitiles-trigger"
+  realm: "ci"
   acl_sets: "ci"
   triggers: "archive"
-  triggers: "desugared_library_head"
-  triggers: "desugared_library_jdk11_head"
+  triggers: "desugared_library-head"
+  triggers: "desugared_library-jdk11_head"
+  triggers: "linux-android-10.0.0"
+  triggers: "linux-android-12.0.0"
   triggers: "linux-android-4.0.4"
   triggers: "linux-android-4.4.4"
   triggers: "linux-android-5.1.1"
   triggers: "linux-android-6.0.1"
   triggers: "linux-android-7.0.0"
-  triggers: "linux-android=10.0.0"
-  triggers: "linux-android=12.0.0"
-  triggers: "linux-android=8.1.0"
-  triggers: "linux-android=9.0.0"
+  triggers: "linux-android-8.1.0"
+  triggers: "linux-android-9.0.0"
   triggers: "linux-d8_jctf"
   triggers: "linux-dex_default"
   triggers: "linux-internal"
   triggers: "linux-jdk11"
   triggers: "linux-jdk8"
   triggers: "linux-jdk9"
-  triggers: "linux-kotlin-dev"
+  triggers: "linux-kotlin_dev"
   triggers: "linux-none"
   triggers: "linux-r8cf_jctf"
   triggers: "linux-run-on-app-dump"
@@ -488,6 +688,14 @@
 acl_sets {
   name: "ci"
   acls {
+    role: OWNER
+    granted_to: "luci-scheduler@appspot.gserviceaccount.com"
+  }
+  acls {
+    role: OWNER
+    granted_to: "group:project-r8-committers"
+  }
+  acls {
     granted_to: "group:all"
   }
 }
diff --git a/infra/config/global/generated/realms.cfg b/infra/config/global/generated/realms.cfg
new file mode 100644
index 0000000..de02ce6
--- /dev/null
+++ b/infra/config/global/generated/realms.cfg
@@ -0,0 +1,67 @@
+# Auto-generated by lucicfg.
+# Do not modify manually.
+#
+# For the schema of this file, see RealmsCfg message:
+#   https://luci-config.appspot.com/schemas/projects:realms.cfg
+
+realms {
+  name: "@root"
+  bindings {
+    role: "role/buildbucket.reader"
+    principals: "group:all"
+  }
+  bindings {
+    role: "role/buildbucket.triggerer"
+    principals: "group:project-r8-committers"
+    principals: "user:luci-scheduler@appspot.gserviceaccount.com"
+  }
+  bindings {
+    role: "role/configs.reader"
+    principals: "group:all"
+  }
+  bindings {
+    role: "role/logdog.reader"
+    principals: "group:all"
+  }
+  bindings {
+    role: "role/logdog.writer"
+    principals: "group:luci-logdog-r8-writers"
+  }
+  bindings {
+    role: "role/scheduler.owner"
+    principals: "group:project-r8-committers"
+    principals: "user:luci-scheduler@appspot.gserviceaccount.com"
+  }
+  bindings {
+    role: "role/scheduler.reader"
+    principals: "group:all"
+  }
+  bindings {
+    role: "role/swarming.poolOwner"
+    principals: "group:mdb/r8-team"
+  }
+  bindings {
+    role: "role/swarming.poolViewer"
+    principals: "group:googlers"
+  }
+}
+realms {
+  name: "ci"
+  bindings {
+    role: "role/buildbucket.builderServiceAccount"
+    principals: "user:r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  }
+  bindings {
+    role: "role/swarming.taskTriggerer"
+    principals: "group:mdb/chrome-troopers"
+    principals: "group:mdb/r8-team"
+  }
+}
+realms {
+  name: "pools/ci"
+  bindings {
+    role: "role/swarming.poolUser"
+    principals: "group:mdb/chrome-troopers"
+    principals: "group:mdb/r8-team"
+  }
+}
diff --git a/infra/config/global/luci-logdog.cfg b/infra/config/global/luci-logdog.cfg
deleted file mode 100644
index 985ab91..0000000
--- a/infra/config/global/luci-logdog.cfg
+++ /dev/null
@@ -1,14 +0,0 @@
-# For the schema of this file and documentation, see ProjectConfig message in
-# https://luci-config.appspot.com/schemas/services/luci-logdog:logdog.cfg
-# This is for the pdfium project, but we're going to piggyback
-# off of the chromium settings.
-
-# Auth groups who can read log streams.
-reader_auth_groups: "project-r8-readers"
-# Auth groups who can register and emit new log streams.
-writer_auth_groups: "luci-logdog-r8-writers"
-# The base Google Storage archival path for this project.
-#
-# Archived LogDog logs will be written to this bucket/path.
-archive_gs_bucket: "logdog-r8-archive"
-
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
deleted file mode 100644
index 2dd3fcb..0000000
--- a/infra/config/global/luci-milo.cfg
+++ /dev/null
@@ -1,241 +0,0 @@
-consoles {
-  id: "main_all"
-  name: "R8 all"
-  repo_url: "https://r8.googlesource.com/r8"
-  refs: "regexp:refs/heads/.*"
-  manifest_name: "REVISION"
-
-  builders {
-    name: "buildbucket/luci.r8.ci/archive"
-    category: "archive"
-    short_name: "archive"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-dex-default"
-    category: "R8"
-    short_name: "dex-default"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-none"
-    category: "R8"
-    short_name: "none"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk8"
-    category: "R8"
-    short_name: "jdk8"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk9"
-    category: "R8"
-    short_name: "jdk9"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk11"
-    category: "R8"
-    short_name: "jdk11"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-4.0.4"
-    category: "R8"
-    short_name: "4.0.4"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-4.4.4"
-    category: "R8"
-    short_name: "4.4.4"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-5.1.1"
-    category: "R8"
-    short_name: "5.1.1"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-6.0.1"
-    category: "R8"
-    short_name: "6.0.1"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-7.0.0"
-    category: "R8"
-    short_name: "7.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-8.1.0"
-    category: "R8"
-    short_name: "8.1.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-9.0.0"
-    category: "R8"
-    short_name: "9.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-10.0.0"
-    category: "R8"
-    short_name: "10.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-12.0.0"
-    category: "R8"
-    short_name: "12.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-internal"
-    category: "R8"
-    short_name: "internal"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump"
-    category: "R8"
-    short_name: "apps-dump"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jctf"
-    category: "R8"
-    short_name: "jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/r8cf-linux-jctf"
-    category: "R8"
-    short_name: "cf-jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-kotlin-dev"
-    category: "R8"
-    short_name: "kotlin-dev"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/windows"
-    category: "win"
-    short_name: "win"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/archive_lib_desugar"
-    category: "library_desugar"
-    short_name: "release"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/desugared_library_head"
-    category: "library_desugar"
-    short_name: "head"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/desugared_library_jdk11_head"
-    category: "library_desugar"
-    short_name: "head_jdk11"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/archive_release"
-    category: "release archive"
-    short_name: "archive"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-dex-default_release"
-    category: "R8 release"
-    short_name: "dex-default"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-none_release"
-    category: "R8 release"
-    short_name: "none"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk8_release"
-    category: "R8 release"
-    short_name: "jdk8"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk9_release"
-    category: "R8 release"
-    short_name: "jdk9"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jdk11_release"
-    category: "R8 release"
-    short_name: "jdk11"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-4.0.4_release"
-    category: "R8 release"
-    short_name: "4.0.4"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-4.4.4_release"
-    category: "R8 release"
-    short_name: "4.4.4"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-5.1.1_release"
-    category: "R8 release"
-    short_name: "5.1.1"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-6.0.1_release"
-    category: "R8 release"
-    short_name: "6.0.1"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-7.0.0_release"
-    category: "R8 release"
-    short_name: "7.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-8.1.0_release"
-    category: "R8 release"
-    short_name: "8.1.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-9.0.0_release"
-    category: "R8 release"
-    short_name: "9.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-10.0.0_release"
-    category: "R8 release"
-    short_name: "10.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-android-12.0.0_release"
-    category: "R8 release"
-    short_name: "12.0.0"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-internal_release"
-    category: "R8 release"
-    short_name: "internal"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump_release"
-    category: "R8 release"
-    short_name: "apps-dump"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/r8cf-linux-jctf_release"
-    category: "R8 release"
-    short_name: "cf-jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-jctf_release"
-    category: "R8 release"
-    short_name: "jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/windows_release"
-    category: "win release"
-    short_name: "win"
-  }
-
-}
-consoles {
-  id: "kotlin"
-  name: "Kotlin"
-  repo_url: "https://github.com/google/kotlin"
-  refs: "regexp:refs/heads/.*"
-  manifest_name: "REVISION"
-  builders {
-    name: "buildbucket/luci.r8.ci/kotlin-builder"
-    category: "kotlin"
-    short_name: "kotlin_builder"
-  }
-}
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
deleted file mode 100644
index 2ed0fc7..0000000
--- a/infra/config/global/luci-notify.cfg
+++ /dev/null
@@ -1,191 +0,0 @@
-# Defines email notifications for builders.
-# See schema at
-# https://chromium.googlesource.com/infra/luci/luci-go/+/master/luci_notify/api/config/notify.proto
-#
-
-notifiers {
-  name: "r8-failures"
-  notifications {
-    on_change: false
-    on_success: false
-    on_failure: true
-    on_new_failure: true
-    # This means send to all people on the blamelist!
-    notify_blamelist {}
-  }
-  builders {
-    name: "archive"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "archive_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-jdk8"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-jdk9"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-4.0.4"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-4.0.4_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-4.4.4"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-4.4.4_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-5.1.1"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-5.1.1_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-6.0.1"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-6.0.1_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-7.0.0"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-7.0.0_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-8.1.0"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-8.1.0_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-9.0.0"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-9.0.0_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-10.0.0"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-10.0.0_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-12.0.0"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-android-12.0.0_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-internal"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-internal_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-run-on-app-dump"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-run-on-app-dump_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-jctf"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-jctf_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "r8cf-linux-jctf"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "r8cf-linux-jctf_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "linux-kotlin-dev"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "windows"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-  builders {
-    name: "windows_release"
-    bucket: "ci"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
deleted file mode 100644
index 6af1a32..0000000
--- a/infra/config/global/luci-scheduler.cfg
+++ /dev/null
@@ -1,730 +0,0 @@
-# Defines jobs on luci-scheduler.appspot.com.
-#
-# For schema of this file and documentation see ProjectConfig message in
-#
-# https://chromium.googlesource.com/infra/luci/luci-go/+/master/scheduler/appengine/messages/config.proto
-
-acl_sets {
-  name: "default"
-  acls {
-    role: READER
-    granted_to: "group:project-r8-readers"
-  }
-  acls {
-    role: OWNER
-    granted_to: "group:project-r8-admins"
-  }
-}
-
-# The format of this file is important, we have a hackish parsing to trigger
-# builds in tools/trigger.py
-trigger {
-  id: "main-gitiles-trigger"
-  acl_sets: "default"
-  gitiles: {
-    repo: "https://r8.googlesource.com/r8"
-    refs: "refs/heads/main"
-  }
-  triggers: "archive"
-  triggers: "linux-dex-default"
-  triggers: "linux-none"
-  triggers: "linux-jdk8"
-  triggers: "linux-jdk9"
-  triggers: "linux-jdk11"
-  triggers: "linux-android-4.0.4"
-  triggers: "linux-android-4.4.4"
-  triggers: "linux-android-5.1.1"
-  triggers: "linux-android-6.0.1"
-  triggers: "linux-android-7.0.0"
-  triggers: "linux-android-8.1.0"
-  triggers: "linux-android-9.0.0"
-  triggers: "linux-android-10.0.0"
-  triggers: "linux-android-12.0.0"
-  triggers: "linux-run-on-app-dump"
-  triggers: "linux-internal"
-  triggers: "linux-jctf"
-  triggers: "r8cf-linux-jctf"
-  triggers: "linux-kotlin-dev"
-  triggers: "windows"
-  triggers: "desugared_library_head"
-  triggers: "desugared_library_jdk11_head"
-}
-
-trigger {
-  id: "desugar_library_trigger"
-  acl_sets: "default"
-  gitiles: {
-    repo: "https://github.googlesource.com/google/desugar_jdk_libs"
-    refs: "refs/heads/master"
-  }
-  triggers: "archive_lib_desugar"
-}
-
-trigger {
-  id: "kotlin_trigger"
-  acl_sets: "default"
-  gitiles: {
-    repo: "https://github.googlesource.com/google/kotlin"
-    refs: "refs/heads/google-ir"
-  }
-  triggers: "kotlin-builder"
-}
-
-trigger {
-  id: "branch-gitiles-trigger"
-  acl_sets: "default"
-  gitiles: {
-    repo: "https://r8.googlesource.com/r8"
-    # Version branches are named d8-x.y (up until d8-1.5) or just x.y (from 1.6)
-    refs: "regexp:refs/heads/(?:d8-)?[0-9]+\\.[0-9]+(\\.[0-9]+)?"
-    path_regexps: "src/main/java/com/android/tools/r8/Version.java"
-  }
-  triggers: "archive_release"
-  triggers: "linux-dex-default_release"
-  triggers: "linux-none_release"
-  triggers: "linux-jdk8_release"
-  triggers: "linux-jdk9_release"
-  triggers: "linux-jdk11_release"
-  triggers: "linux-android-4.0.4_release"
-  triggers: "linux-android-4.4.4_release"
-  triggers: "linux-android-5.1.1_release"
-  triggers: "linux-android-6.0.1_release"
-  triggers: "linux-android-7.0.0_release"
-  triggers: "linux-android-8.1.0_release"
-  triggers: "linux-android-9.0.0_release"
-  triggers: "linux-android-10.0.0_release"
-  triggers: "linux-android-12.0.0_release"
-  triggers: "linux-internal_release"
-  triggers: "linux-jctf_release"
-  triggers: "r8cf-linux-jctf_release"
-  triggers: "windows_release"
-}
-
-trigger {
-  id: "app-dump-gitiles-trigger"
-  acl_sets: "default"
-  gitiles: {
-    repo: "https://r8.googlesource.com/r8"
-    # Only trigger apps from 3.0 (this works until we reach version 10)
-    refs: "regexp:refs/heads/[3-9]+\\.[0-9]+(\\.[0-9]+)?"
-    path_regexps: "src/main/java/com/android/tools/r8/Version.java"
-  }
-  triggers: "linux-run-on-app-dump_release"
-}
-
-
-job {
-  id: "archive"
-  acl_sets: "default"
-  triggering_policy: {
-    max_concurrent_invocations: 3
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "archive"
-  }
-}
-
-job {
-  id: "archive_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "archive_release"
-  }
-}
-
-job {
-  id: "archive_lib_desugar"
-  acl_sets: "default"
-  triggering_policy: {
-    max_concurrent_invocations: 1
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "archive_lib_desugar"
-  }
-}
-
-job {
-  id: "desugared_library_head"
-  acl_sets: "default"
-  triggering_policy: {
-    max_concurrent_invocations: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "desugared_library_head"
-  }
-}
-
-job {
-  id: "desugared_library_jdk11_head"
-  acl_sets: "default"
-  triggering_policy: {
-    max_concurrent_invocations: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "desugared_library_jdk11_head"
-  }
-}
-
-job {
-  id: "kotlin-builder"
-  acl_sets: "default"
-  triggering_policy: {
-    max_concurrent_invocations: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "kotlin-builder"
-  }
-}
-
-job {
-  id: "linux-dex-default"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-dex-default"
-  }
-}
-
-job {
-  id: "linux-none"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-none"
-  }
-}
-
-job {
-  id: "linux-jdk8"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 2
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jdk8"
-  }
-}
-
-job {
-  id: "linux-jdk8_release"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 2
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jdk8_release"
-  }
-}
-
-job {
-  id: "linux-jdk9"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 2
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jdk9"
-  }
-}
-
-job {
-  id: "linux-jdk9_release"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 2
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jdk9_release"
-  }
-}
-
-job {
-  id: "linux-jdk11"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 2
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jdk11"
-  }
-}
-
-job {
-  id: "linux-jdk11_release"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 2
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jdk11_release"
-  }
-}
-
-job {
-  id: "linux-android-4.0.4"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-4.0.4"
-  }
-}
-
-job {
-  id: "linux-android-4.0.4_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-4.0.4_release"
-  }
-}
-
-job {
-  id: "linux-android-4.4.4"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-4.4.4"
-  }
-}
-
-job {
-  id: "linux-android-4.4.4_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-4.4.4_release"
-  }
-}
-
-job {
-  id: "linux-android-5.1.1"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-5.1.1"
-  }
-}
-
-job {
-  id: "linux-android-5.1.1_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-5.1.1_release"
-  }
-}
-
-job {
-  id: "linux-android-6.0.1"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-6.0.1"
-  }
-}
-
-job {
-  id: "linux-android-6.0.1_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-6.0.1_release"
-  }
-}
-
-job {
-  id: "linux-android-7.0.0"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-7.0.0"
-  }
-}
-
-job {
-  id: "linux-android-7.0.0_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-7.0.0_release"
-  }
-}
-
-job {
-  id: "linux-android-8.1.0"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-8.1.0"
-  }
-}
-
-job {
-  id: "linux-android-8.1.0_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-8.1.0_release"
-  }
-}
-
-
-job {
-  id: "linux-android-9.0.0"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-9.0.0"
-  }
-}
-
-job {
-  id: "linux-android-9.0.0_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-9.0.0_release"
-  }
-}
-
-job {
-  id: "linux-android-10.0.0"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-10.0.0"
-  }
-}
-
-job {
-  id: "linux-android-10.0.0_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-10.0.0_release"
-  }
-}
-
-job {
-  id: "linux-android-12.0.0"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 6
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-12.0.0"
-  }
-}
-
-job {
-  id: "linux-android-12.0.0_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-android-12.0.0_release"
-  }
-}
-
-job {
-  id: "linux-internal"
-  acl_sets: "default"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-internal"
-  }
-}
-
-job {
-  id: "linux-internal_release"
-  acl_sets: "default"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-internal_release"
-  }
-}
-
-job {
-  id: "linux-run-on-app-dump"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-run-on-app-dump"
-  }
-}
-
-job {
-  id: "linux-run-on-app-dump_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-run-on-app-dump_release"
-  }
-}
-
-job {
-  id: "linux-jctf"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jctf"
-  }
-}
-
-job {
-  id: "linux-jctf_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-jctf_release"
-  }
-}
-
-job {
-  id: "linux-dex-default_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-dex-default_release"
-  }
-}
-
-job {
-  id: "linux-none_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-    max_concurrent_invocations: 3
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-none_release"
-  }
-}
-
-job {
-  id: "r8cf-linux-jctf"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "r8cf-linux-jctf"
-  }
-}
-
-job {
-  id: "r8cf-linux-jctf_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "r8cf-linux-jctf_release"
-  }
-}
-
-job {
-  id: "linux-kotlin-dev"
-  acl_sets: "default"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 2
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "linux-kotlin-dev"
-  }
-}
-
-job {
-  id: "windows"
-  triggering_policy: {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 3
-  }
-  acl_sets: "default"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "windows"
-  }
-}
-
-job {
-  id: "windows_release"
-  acl_sets: "default"
-  triggering_policy: {
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.r8.ci"
-    builder: "windows_release"
-  }
-}
-
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 5e790b5..0d94768 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -1,5 +1,14 @@
 #!/usr/bin/env lucicfg
 
+lucicfg.check_version("1.28.0", "Please use newer `lucicfg` binary")
+
+# Enable LUCI Realms support.
+lucicfg.enable_experiment("crbug.com/1085650")
+
+# Launch 0% of Builds in "realms-aware mode"
+luci.builder.defaults.experiments.set({"luci.use_realms": 100})
+
+
 luci.project(
     name = "r8",
     buildbucket = "cr-buildbucket.appspot.com",
@@ -21,6 +30,8 @@
         acl.entry(
             [
                 acl.BUILDBUCKET_TRIGGERER,
+                acl.SCHEDULER_OWNER,
+
             ],
             groups = [
                 "project-r8-committers"
@@ -29,8 +40,54 @@
                 "luci-scheduler@appspot.gserviceaccount.com"
             ]
         ),
+        acl.entry(
+            [
+                acl.LOGDOG_WRITER,
+            ],
+            groups = [
+                "luci-logdog-r8-writers"
+            ],
+        ),
+    ],
+    bindings = [
+        luci.binding(
+            roles = "role/swarming.poolOwner",
+            groups = "mdb/r8-team",
+        ),
+        luci.binding(
+            roles = "role/swarming.poolViewer",
+            groups = "googlers",
+        ),
+    ],
+)
 
-    ]
+luci.logdog(gs_bucket = "logdog-r8-archive")
+
+
+# Allow the given users to use LUCI `led` tool and "Debug" button
+# inside the given bucket & pool security realms.
+def led_users(*, pool_realm, builder_realm, groups):
+    luci.realm(
+        name = pool_realm,
+        bindings = [
+            luci.binding(
+                roles = "role/swarming.poolUser",
+                groups = groups,
+            ),
+        ],
+    )
+    luci.binding(
+        realm = builder_realm,
+        roles = "role/swarming.taskTriggerer",
+        groups = groups,
+    )
+led_users(
+    pool_realm="pools/ci",
+    builder_realm="ci",
+    groups=[
+        "mdb/r8-team",
+        "mdb/chrome-troopers",
+    ],
 )
 
 luci.bucket(name = "ci")
@@ -55,7 +112,7 @@
   bucket = "ci",
   repo = "https://r8.googlesource.com/r8",
   # Version branches are named d8-x.y (up until d8-1.5) or just x.y (from 1.6)
-  refs = ["regexp:refs/heads/(?:d8-)?[0-9]+\\.[0-9]+(\\.[0-9]+)?"],
+  refs = ["refs/heads/(?:d8-)?[0-9]+\\.[0-9]+(\\.[0-9]+)?"],
   path_regexps = ["src/main/java/com/android/tools/r8/Version.java"]
 )
 
@@ -63,16 +120,14 @@
     name = "main",
     title = "R8 Main Console",
     repo = "https://r8.googlesource.com/r8",
-    refs = ["regexp:refs/heads/.*"]
+    refs = ["refs/heads/.*"]
 )
 
+
+view_builders = []
+
 def builder_view(name, category, short_name):
-    return luci.console_view_entry(
-        console_view = "main",
-        builder = name,
-        category = category,
-        short_name = short_name,
-    )
+  view_builders.append((name, category, short_name))
 
 luci.recipe(
       name="rex",
@@ -96,7 +151,7 @@
     "pool" : "luci.r8.ci"
   }
   if windows:
-    dimensions["os"] = "windows-10"
+    dimensions["os"] = "Windows-10"
   else:
     dimensions["os"] = "Ubuntu-16.04"
   if jctf:
@@ -107,10 +162,14 @@
     dimensions["normal"] = "true"
   return dimensions
 
-def r8_builder(name, priority=26, trigger=True, **kwargs):
+def r8_builder(name, priority=26, trigger=True, category=None,
+               triggering_policy=None, **kwargs):
   release = name.endswith("release")
   triggered = None if not trigger else ["branch-gitiles-trigger"] if release\
       else ["main-gitiles-trigger"]
+  triggering_policy = triggering_policy or scheduler.policy(
+      kind = scheduler.GREEDY_BATCHING_KIND,
+      max_concurrent_invocations = 4)
 
   luci.builder(
     name = name,
@@ -122,21 +181,25 @@
     notifies = ["r8-failures"] if trigger else None,
     priority = priority,
     triggered_by = triggered,
+    triggering_policy = triggering_policy,
     executable = "rex",
     **kwargs
   )
-  category = "R8 release" if release else "R8"
-  builder_view(name, category, name.split("-")[-1])
+  category = category if category else "R8"
+  category = "Release|" + category if release else category
+  builder_view(name, category, name.split("-")[-1].replace("_release", ""))
 
 def r8_tester(name,
     test_options,
     dimensions = None,
     execution_timeout = time.hour * 6,
-    expiration_timeout = time.hour * 35):
+    expiration_timeout = time.hour * 35,
+    category=None):
   dimensions = dimensions if dimensions else get_dimensions(normal=True)
   for name in [name, name + "_release"]:
     r8_builder(
         name = name,
+        category = category,
         execution_timeout = execution_timeout,
         expiration_timeout = expiration_timeout,
         dimensions = dimensions,
@@ -146,28 +209,81 @@
         }
     )
 
-def r8_tester_with_default(name, test_options, dimensions=None):
-  r8_tester(name, test_options + common_test_options, dimensions)
+def r8_tester_with_default(name, test_options, dimensions=None, category=None):
+  r8_tester(name, test_options + common_test_options,
+            dimensions = dimensions, category = category)
 
-def jctf():
-  for release in ["", "_release"]:
-    for tool in ["d8", "r8cf"]:
-      properties = {
-          "tool": tool,
-          "builder_group" : "internal.client.r8",
-          "dex_vm" : "all",
-          "only_jctf" : "true",
-      }
-      name = "linux-" + tool + "_jctf"
-      name = name + release
-      r8_builder(
-          name,
-          dimensions = get_dimensions(jctf=True),
-          execution_timeout = time.hour * 12,
-          expiration_timeout = time.hour * 35,
-          properties = properties,
-      )
-jctf()
+def archivers():
+  for name in ["archive", "archive_release", "lib_desugar-archive"]:
+    desugar = "desugar" in name
+    properties = {
+        "test_wrapper" : "tools/archive_desugar_jdk_libs.py" if desugar else "tools/archive.py",
+        "builder_group" : "internal.client.r8"
+    }
+    r8_builder(
+        name,
+        category = "library_desugar" if desugar else "archive",
+        dimensions = get_dimensions(),
+        triggering_policy = scheduler.policy(
+            kind = scheduler.GREEDY_BATCHING_KIND,
+            max_batch_size = 1,
+            max_concurrent_invocations = 3
+        ),
+        priority = 25,
+        trigger = not desugar,
+        properties = properties,
+        execution_timeout = time.hour * 1 if desugar else time.minute * 30 ,
+        expiration_timeout = time.hour * 35,
+    )
+archivers()
+
+r8_tester_with_default("linux-dex_default", ["--runtimes=dex-default"])
+r8_tester_with_default("linux-none", ["--runtimes=none"])
+r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"])
+r8_tester_with_default("linux-jdk9", ["--runtimes=jdk9"])
+r8_tester_with_default("linux-jdk11", ["--runtimes=jdk11"])
+
+r8_tester_with_default("linux-android-4.0.4",
+    ["--dex_vm=4.0.4", "--all_tests"])
+r8_tester_with_default("linux-android-4.4.4",
+    ["--dex_vm=4.4.4", "--all_tests"])
+r8_tester_with_default("linux-android-5.1.1",
+    ["--dex_vm=5.1.1", "--all_tests"])
+r8_tester_with_default("linux-android-6.0.1",
+    ["--dex_vm=6.0.1", "--all_tests"])
+r8_tester_with_default("linux-android-7.0.0",
+    ["--dex_vm=7.0.0", "--all_tests"])
+r8_tester_with_default("linux-android-8.1.0",
+    ["--dex_vm=8.1.0", "--all_tests"])
+r8_tester_with_default("linux-android-9.0.0",
+    ["--dex_vm=9.0.0", "--all_tests"])
+r8_tester_with_default("linux-android-10.0.0",
+    ["--dex_vm=10.0.0", "--all_tests"])
+r8_tester_with_default("linux-android-12.0.0",
+    ["--dex_vm=12.0.0", "--all_tests"])
+
+r8_tester_with_default("windows", ["--all_tests"],
+    dimensions=get_dimensions(windows=True))
+
+def internal():
+  for name in ["linux-internal", "linux-internal_release"]:
+    r8_builder(
+        name,
+        dimensions = get_dimensions(internal=True),
+        triggering_policy = scheduler.policy(
+            kind = scheduler.GREEDY_BATCHING_KIND,
+            max_concurrent_invocations = 1
+        ),
+        priority = 25,
+        properties = {
+            "test_options" : ["--bot"],
+            "test_wrapper" : "tools/internal_test.py",
+            "builder_group" : "internal.client.r8"
+        },
+        execution_timeout = time.hour * 12,
+        expiration_timeout = time.hour * 35,
+    )
+internal()
 
 def app_dump():
   for release in ["", "_release"]:
@@ -195,9 +311,10 @@
        "builder_group" : "internal.client.r8",
        "test_options" : test_options,
     }
-    name = "desugared_library_" + name
+    name = "desugared_library-" + name
     r8_builder(
         name,
+        category = "library_desugar",
         dimensions = get_dimensions(),
         execution_timeout = time.hour * 12,
         expiration_timeout = time.hour * 35,
@@ -205,81 +322,8 @@
     )
 desugared_library()
 
-def archivers():
-  for name in ["archive", "archive_release", "archive_lib_desugar"]:
-    desugar = "desugar" in name
-    properties = {
-        "archive": "true",
-        "builder_group" : "internal.client.r8"
-    }
-    if desugar:
-      properties["sdk_desugar"] = "true"
-    r8_builder(
-        name,
-        dimensions = get_dimensions(),
-        triggering_policy = scheduler.policy(
-            kind = scheduler.GREEDY_BATCHING_KIND,
-            max_batch_size = 1,
-            max_concurrent_invocations = 3
-        ),
-        priority = 25,
-        trigger = not desugar,
-        properties = properties,
-        execution_timeout = time.hour * 1 if desugar else time.minute * 30 ,
-        expiration_timeout = time.hour * 35,
-    )
-archivers()
-
-def internal():
-  for name in ["linux-internal", "linux-internal_release"]:
-    r8_builder(
-        name,
-        dimensions = get_dimensions(internal=True),
-        triggering_policy = scheduler.policy(
-            kind = scheduler.GREEDY_BATCHING_KIND,
-            max_batch_size = 1,
-            max_concurrent_invocations = 1
-        ),
-        priority = 25,
-        properties = {
-            "internal": "true",
-            "builder_group" : "internal.client.r8"
-        },
-        execution_timeout = time.hour * 12,
-        expiration_timeout = time.hour * 35,
-    )
-internal()
-
-r8_tester_with_default("linux-dex_default", ["--runtimes=dex-default"])
-r8_tester_with_default("linux-none", ["--runtimes=none"])
-r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"])
-r8_tester_with_default("linux-jdk9", ["--runtimes=jdk9"])
-r8_tester_with_default("linux-jdk11", ["--runtimes=jdk11"])
-
-r8_tester_with_default("linux-android-4.0.4",
-    ["--dex_vm=4.0.4", "--all_tests"])
-r8_tester_with_default("linux-android-4.4.4",
-    ["--dex_vm=4.4.4", "--all_tests"])
-r8_tester_with_default("linux-android-5.1.1",
-    ["--dex_vm=5.1.1", "--all_tests"])
-r8_tester_with_default("linux-android-6.0.1",
-    ["--dex_vm=6.0.1", "--all_tests"])
-r8_tester_with_default("linux-android-7.0.0",
-    ["--dex_vm=7.0.0", "--all_tests"])
-r8_tester_with_default("linux-android=8.1.0",
-    ["--dex_vm=8.1.0", "--all_tests"])
-r8_tester_with_default("linux-android=9.0.0",
-    ["--dex_vm=9.0.0", "--all_tests"])
-r8_tester_with_default("linux-android=10.0.0",
-    ["--dex_vm=10.0.0", "--all_tests"])
-r8_tester_with_default("linux-android=12.0.0",
-    ["--dex_vm=12.0.0", "--all_tests"])
-
-r8_tester_with_default("windows", ["--all_tests"],
-    dimensions=get_dimensions(windows=True))
-
 r8_builder(
-    "linux-kotlin-dev",
+    "linux-kotlin_dev",
     dimensions = get_dimensions(),
     execution_timeout = time.hour * 12,
     expiration_timeout = time.hour * 35,
@@ -288,3 +332,52 @@
       "test_options" : ["--runtimes=dex-default:jdk11", "--kotlin-compiler-dev", "--one_line_per_test", "--archive_failures", "--no-internal", "*kotlin*"]
     }
 )
+
+def jctf():
+  for release in ["", "_release"]:
+    for tool in ["d8", "r8cf"]:
+      properties = {
+          "test_options" : [
+              "--no_internal",
+              "--one_line_per_test",
+              "--archive_failures",
+              "--dex_vm=all",
+              "--tool=" + tool,
+              "--only_jctf"],
+          "builder_group" : "internal.client.r8",
+      }
+      name = "linux-" + tool + "_jctf" + release
+      r8_builder(
+          name,
+          category = "jctf",
+          dimensions = get_dimensions(jctf=True),
+          execution_timeout = time.hour * 12,
+          expiration_timeout = time.hour * 35,
+          properties = properties,
+      )
+jctf()
+
+order_of_categories = [
+  "archive",
+  "R8",
+  "jctf",
+  "library_desugar",
+  "Release|archive",
+  "Release|R8",
+  "Release|jctf"
+]
+
+def add_view_entries():
+  # Ensure that all categories are ordered
+  for v in view_builders:
+    if not v[1] in order_of_categories:
+      fail()
+  for category in order_of_categories:
+    for v in [x for x in view_builders if x[1] == category]:
+      luci.console_view_entry(
+          console_view = "main",
+          builder = v[0],
+          category = v[1],
+          short_name = v[2]
+      )
+add_view_entries()
\ No newline at end of file
diff --git a/infra/config/global/project.cfg b/infra/config/global/project.cfg
deleted file mode 100644
index 4ae5cd5..0000000
--- a/infra/config/global/project.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-# For the schema of this file and documentation, see ProjectCfg message in
-# https://luci-config.appspot.com/schemas/projects:project.cfg
-name: "r8"
-access: "group:all" # public
-
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 65a9a2f..22dd97f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
-import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -37,6 +36,7 @@
 import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
@@ -50,13 +50,10 @@
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterLibraryTypeSynthesizer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.records.RecordRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
-import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
 import com.android.tools.r8.ir.optimize.SwitchMapCollector;
-import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
-import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLens;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
@@ -318,7 +315,7 @@
         EnumUnboxingCfMethods.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
       if (options.shouldDesugarRecords()) {
-        RecordRewriter.registerSynthesizedCodeReferences(appView.dexItemFactory());
+        RecordDesugaring.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
       CfUtilityMethodsForCodeOptimizations.registerSynthesizedCodeReferences(
           appView.dexItemFactory());
@@ -510,17 +507,6 @@
         }
         assert appView.verticallyMergedClasses() != null;
 
-        if (options.enableUninstantiatedTypeOptimization) {
-          timing.begin("UninstantiatedTypeOptimization");
-          UninstantiatedTypeOptimizationGraphLens lens =
-              new UninstantiatedTypeOptimization(appViewWithLiveness)
-                  .strenghtenOptimizationInfo()
-                  .run(new MethodPoolCollection(appViewWithLiveness), executorService, timing);
-          assert lens == null || getDirectApp(appView).verifyNothingToRewrite(appView, lens);
-          appView.rewriteWithLens(lens);
-          timing.end();
-        }
-
         HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
             .runIfNecessary(runtimeTypeCheckInfo, executorService, timing);
       }
@@ -1060,11 +1046,12 @@
             enqueuer.getGraphReporter().getGraphNode(reference), System.out);
       }
     }
-    if (rootSet.checkDiscarded.isEmpty()
-        || appView.options().testing.dontReportFailingCheckDiscarded) {
+    if (appView.options().testing.dontReportFailingCheckDiscarded) {
       return;
     }
-    List<DexDefinition> failed = new DiscardedChecker(rootSet, classes.get()).run();
+    DiscardedChecker discardedChecker =
+        forMainDex ? DiscardedChecker.createForMainDex(appView) : DiscardedChecker.create(appView);
+    List<ProgramDefinition> failed = discardedChecker.run(classes.get(), executorService);
     if (failed.isEmpty()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 08e18ca4..e469f0a 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import com.android.tools.r8.graph.ApplicationReaderMap;
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplicationReadFlags;
@@ -366,11 +367,13 @@
       }
       // Read the DexCode items and DexProgramClass items in parallel.
       if (!options.skipReadingDexCode) {
+        ApplicationReaderMap applicationReaderMap = ApplicationReaderMap.getInstance(options);
         for (DexParser<DexProgramClass> dexParser : dexParsers) {
           futures.add(
               executorService.submit(
                   () -> {
-                    dexParser.addClassDefsTo(classes::add); // Depends on Methods, Code items etc.
+                    dexParser.addClassDefsTo(
+                        classes::add, applicationReaderMap); // Depends on Methods, Code items etc.
                   }));
         }
       }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 3b2c1df..6c02456 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -218,12 +218,12 @@
     Collection<DexProgramClass> classes = appView.appInfo().classes();
     Reference2LongMap<DexString> inputChecksums = new Reference2LongOpenHashMap<>(classes.size());
     for (DexProgramClass clazz : classes) {
-      inputChecksums.put(clazz.getType().descriptor, clazz.getChecksum());
+      inputChecksums.put(namingLens.lookupDescriptor(clazz.getType()), clazz.getChecksum());
     }
     for (VirtualFile file : files) {
       ClassesChecksum toWrite = new ClassesChecksum();
       for (DexProgramClass clazz : file.classes()) {
-        DexString desc = clazz.type.descriptor;
+        DexString desc = namingLens.lookupDescriptor(clazz.type);
         toWrite.addChecksum(desc.toString(), inputChecksums.getLong(desc));
       }
       file.injectString(appView.dexItemFactory().createString(toWrite.toJsonString()));
@@ -243,7 +243,7 @@
 
     if (markers != null && !markers.isEmpty()) {
       if (proguardMapId != null) {
-        markers.get(0).setPgMapId(proguardMapId.get());
+        markers.get(0).setPgMapId(proguardMapId.getId());
       }
       markerStrings = new ArrayList<>(markers.size());
       for (Marker marker : markers) {
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 03c50c0..03e0e31 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -80,7 +80,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -715,7 +714,7 @@
     return methods;
   }
 
-  void addClassDefsTo(Consumer<T> classCollection) {
+  void addClassDefsTo(Consumer<T> classCollection, ApplicationReaderMap applicationReaderMap) {
     final DexSection dexSection = lookupSection(Constants.TYPE_CLASS_DEF_ITEM);
     final int length = dexSection.length;
     indexedItems.initializeClasses(length);
@@ -768,7 +767,8 @@
 
       Long checksum = null;
       if (checksums != null && !checksums.isEmpty()) {
-        String desc = type.toDescriptorString();
+        DexType originalType = applicationReaderMap.getInvertedType(type);
+        String desc = originalType.toDescriptorString();
         checksum = checksums.getOrDefault(desc, null);
         if (!options.dexClassChecksumFilter.test(desc, checksum)) {
           continue;
@@ -1003,11 +1003,11 @@
   private void populateTypes() {
     DexSection dexSection = lookupSection(Constants.TYPE_TYPE_ID_ITEM);
     assert verifyOrderOfTypeIds(dexSection);
-    Map<DexType, DexType> typeMap = ApplicationReaderMap.getTypeMap(options);
+    ApplicationReaderMap applicationReaderMap = ApplicationReaderMap.getInstance(options);
     indexedItems.initializeTypes(dexSection.length);
     for (int i = 0; i < dexSection.length; i++) {
       DexType type = typeAt(i);
-      DexType actualType = typeMap.getOrDefault(type, type);
+      DexType actualType = applicationReaderMap.getType(type);
       indexedItems.setType(i, actualType);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java b/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java
index 67e74bf..ce54533 100644
--- a/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/CheckDiscardDiagnostic.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.shaking.GraphReporter;
@@ -24,15 +24,18 @@
     private ImmutableList.Builder<String> messagesBuilder = ImmutableList.builder();
 
     public Builder addFailedItems(
-        List<DexDefinition> failed,
+        List<ProgramDefinition> failed,
         GraphReporter graphReporter,
         WhyAreYouKeepingConsumer whyAreYouKeepingConsumer) {
-      for (DexDefinition definition : failed) {
+      for (ProgramDefinition definition : failed) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         whyAreYouKeepingConsumer.printWhyAreYouKeeping(
             graphReporter.getGraphNode(definition.getReference()), new PrintStream(baos));
         messagesBuilder.add(
-            "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString());
+            "Item "
+                + definition.getReference().toSourceString()
+                + " was not discarded.\n"
+                + baos.toString());
       }
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 0225c1d..99b9cb7 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -561,6 +561,10 @@
     }
   }
 
+  public boolean hasVerticallyMergedClasses() {
+    return verticallyMergedClasses != null;
+  }
+
   /**
    * Get the result of vertical class merging. Returns null if vertical class merging has not been
    * run.
diff --git a/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
index 5ce8224..496066b3 100644
--- a/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
+++ b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
@@ -5,27 +5,65 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
 
-public class ApplicationReaderMap {
+public abstract class ApplicationReaderMap {
 
-  public static Map<String, String> getDescriptorMap(InternalOptions options) {
-    ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+  public static ApplicationReaderMap INSTANCE;
+
+  public abstract String getDescriptor(String descriptor);
+
+  public abstract DexType getType(DexType type);
+
+  public abstract DexType getInvertedType(DexType type);
+
+  public static ApplicationReaderMap getInstance(InternalOptions options) {
     if (options.shouldDesugarRecords() && !options.testing.disableRecordApplicationReaderMap) {
-      builder.put(DexItemFactory.recordTagDescriptorString, DexItemFactory.recordDescriptorString);
+      return new RecordMap(options.dexItemFactory());
     }
-    return builder.build();
+    return new EmptyMap();
   }
 
-  public static Map<DexType, DexType> getTypeMap(InternalOptions options) {
-    DexItemFactory factory = options.dexItemFactory();
-    ImmutableMap.Builder<DexType, DexType> builder = ImmutableMap.builder();
-    getDescriptorMap(options)
-        .forEach(
-            (k, v) -> {
-              builder.put(factory.createType(k), factory.createType(v));
-            });
-    return builder.build();
+  public static class EmptyMap extends ApplicationReaderMap {
+
+    @Override
+    public String getDescriptor(String descriptor) {
+      return descriptor;
+    }
+
+    @Override
+    public DexType getType(DexType type) {
+      return type;
+    }
+
+    @Override
+    public DexType getInvertedType(DexType type) {
+      return type;
+    }
+  }
+
+  public static class RecordMap extends ApplicationReaderMap {
+
+    private final DexItemFactory factory;
+
+    public RecordMap(DexItemFactory factory) {
+      this.factory = factory;
+    }
+
+    @Override
+    public String getDescriptor(String descriptor) {
+      return descriptor.equals(DexItemFactory.recordTagDescriptorString)
+          ? DexItemFactory.recordDescriptorString
+          : descriptor;
+    }
+
+    @Override
+    public DexType getType(DexType type) {
+      return type == factory.recordTagType ? factory.recordType : type;
+    }
+
+    @Override
+    public DexType getInvertedType(DexType type) {
+      return type == factory.recordType ? factory.recordTagType : type;
+    }
   }
 }
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 82f09f8..450bc51 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -217,7 +217,7 @@
   }
 
   public int getNumberOfArguments() {
-    return getReference().getArity() + BooleanUtils.intValue(isInstance());
+    return getReference().getNumberOfArguments(isStatic());
   }
 
   public CompilationState getCompilationState() {
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 82fa005..d31d8b7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2131,6 +2131,11 @@
     return sb.toString();
   }
 
+  public <T> T createFreshMember(
+      Function<DexString, Optional<T>> tryString, String baseName, DexType holder) {
+    return createFreshMember(tryString, baseName, holder, 0);
+  }
+
   /**
    * Find a fresh method name that is not used by any other method. The method name takes the form
    * "basename$holdername" or "basename$holdername$index".
@@ -2138,15 +2143,16 @@
    * @param tryString callback to check if the method name is in use.
    */
   public <T> T createFreshMember(
-      Function<DexString, Optional<T>> tryString, String baseName, DexType holder) {
-    int index = 0;
+      Function<DexString, Optional<T>> tryString, String baseName, DexType holder, int index) {
+    int offset = 0;
     while (true) {
-      assert index < 1000;
-      DexString name = createString(createMemberString(baseName, holder, index++));
+      assert offset < 1000;
+      DexString name = createString(createMemberString(baseName, holder, index + offset));
       Optional<T> result = tryString.apply(name);
       if (result.isPresent()) {
         return result.get();
       }
+      offset++;
     }
   }
 
@@ -2216,14 +2222,28 @@
     return internalCreateFreshMethodNameWithHolder(baseName, holder, proto, target, isFresh);
   }
 
+  public DexMethod createFreshMethodNameWithoutHolder(
+      String baseName, DexProto proto, DexType target, Predicate<DexMethod> isFresh) {
+    return createFreshMethodNameWithoutHolder(baseName, proto, target, isFresh, 0);
+  }
+
   /**
    * Tries to find a method name for insertion into the class {@code target} of the form baseName$n,
    * where {@code baseName} is supplied by the user, and {@code n} is picked to be the first number
-   * so that {@code isFresh.apply(method)} returns {@code true}.
+   * starting from {@param index} so that {@code isFresh.apply(method)} returns {@code true}.
    */
   public DexMethod createFreshMethodNameWithoutHolder(
-      String baseName, DexProto proto, DexType target, Predicate<DexMethod> isFresh) {
-    return internalCreateFreshMethodNameWithHolder(baseName, null, proto, target, isFresh);
+      String baseName, DexProto proto, DexType target, Predicate<DexMethod> isFresh, int index) {
+    return internalCreateFreshMethodNameWithHolder(baseName, null, proto, target, isFresh, index);
+  }
+
+  private DexMethod internalCreateFreshMethodNameWithHolder(
+      String baseName,
+      DexType holder,
+      DexProto proto,
+      DexType target,
+      Predicate<DexMethod> isFresh) {
+    return internalCreateFreshMethodNameWithHolder(baseName, holder, proto, target, isFresh, 0);
   }
 
   /**
@@ -2235,7 +2255,8 @@
       DexType holder,
       DexProto proto,
       DexType target,
-      Predicate<DexMethod> isFresh) {
+      Predicate<DexMethod> isFresh,
+      int index) {
     return createFreshMember(
         name -> {
           DexMethod tryMethod = createMethod(target, proto, name);
@@ -2246,7 +2267,8 @@
           }
         },
         baseName,
-        holder);
+        holder,
+        index);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index c2eeffa..d7f282f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
@@ -69,6 +70,10 @@
     return getParameter(argumentIndex - 1);
   }
 
+  public int getNumberOfArguments(boolean isStatic) {
+    return getArity() + BooleanUtils.intValue(!isStatic);
+  }
+
   public DexType getParameter(int index) {
     return proto.getParameter(index);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 047be58..ca751d0 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -4,11 +4,10 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.ir.desugar.records.RecordRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import org.objectweb.asm.Type;
 
@@ -25,13 +24,13 @@
   private final ConcurrentHashMap<String, Type> asmObjectTypeCache = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<String, Type> asmTypeCache = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
-  private final Map<String, String> typeDescriptorMap;
+  private final ApplicationReaderMap applicationReaderMap;
 
   private boolean hasReadRecordReferenceFromProgramClass = false;
 
   public JarApplicationReader(InternalOptions options) {
     this.options = options;
-    typeDescriptorMap = ApplicationReaderMap.getDescriptorMap(options);
+    applicationReaderMap = ApplicationReaderMap.getInstance(options);
   }
 
   public Type getAsmObjectType(String name) {
@@ -61,7 +60,7 @@
 
   public DexType getTypeFromDescriptor(String desc) {
     assert isValidDescriptor(desc);
-    String actualDesc = typeDescriptorMap.getOrDefault(desc, desc);
+    String actualDesc = applicationReaderMap.getDescriptor(desc);
     return options.itemFactory.createType(getString(actualDesc));
   }
 
@@ -162,13 +161,14 @@
   }
 
   public void checkFieldForRecord(DexField dexField) {
-    if (options.shouldDesugarRecords() && RecordRewriter.refersToRecord(dexField, getFactory())) {
+    if (options.shouldDesugarRecords() && RecordDesugaring.refersToRecord(dexField, getFactory())) {
       setHasReadRecordReferenceFromProgramClass();
     }
   }
 
   public void checkMethodForRecord(DexMethod dexMethod) {
-    if (options.shouldDesugarRecords() && RecordRewriter.refersToRecord(dexMethod, getFactory())) {
+    if (options.shouldDesugarRecords()
+        && RecordDesugaring.refersToRecord(dexMethod, getFactory())) {
       setHasReadRecordReferenceFromProgramClass();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
index 1b49b31..00bd8bb 100644
--- a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -211,14 +211,19 @@
       //  unreachable.
       DexMethod newMethod = methodMap.apply(previous.getReference());
       if (newMethod == null) {
+        newMethod = previous.getReference();
+      }
+      RewrittenPrototypeDescription newPrototypeChanges =
+          internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod);
+      if (newMethod == previous.getReference()
+          && newPrototypeChanges == previous.getPrototypeChanges()) {
         return previous;
       }
       // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
       //  that only subclasses which are known to need it actually do it?
       return MethodLookupResult.builder(this)
           .setReference(newMethod)
-          .setPrototypeChanges(
-              internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
+          .setPrototypeChanges(newPrototypeChanges)
           .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
           .build();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index a4a886f..5328885 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -4,11 +4,12 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
@@ -183,26 +184,29 @@
 
     private final DexType oldType;
     private final DexType newType;
+    private final SingleValue singleValue;
 
-    static RewrittenTypeInfo toVoid(DexType oldReturnType, DexItemFactory dexItemFactory) {
-      return new RewrittenTypeInfo(oldReturnType, dexItemFactory.voidType);
+    public static RewrittenTypeInfo toVoid(
+        DexType oldReturnType, DexItemFactory dexItemFactory, SingleValue singleValue) {
+      assert singleValue != null;
+      return new RewrittenTypeInfo(oldReturnType, dexItemFactory.voidType, singleValue);
     }
 
     public RewrittenTypeInfo(DexType oldType, DexType newType) {
+      this(oldType, newType, null);
+    }
+
+    public RewrittenTypeInfo(DexType oldType, DexType newType, SingleValue singleValue) {
       this.oldType = oldType;
       this.newType = newType;
+      this.singleValue = singleValue;
+      assert !newType.isVoidType() || singleValue != null;
     }
 
     public RewrittenTypeInfo combine(RewrittenPrototypeDescription other) {
       return other.hasRewrittenReturnInfo() ? combine(other.getRewrittenReturnInfo()) : this;
     }
 
-    public RewrittenTypeInfo combine(RewrittenTypeInfo other) {
-      assert !getNewType().isVoidType();
-      assert getNewType() == other.getOldType();
-      return new RewrittenTypeInfo(getOldType(), other.getNewType());
-    }
-
     public DexType getNewType() {
       return newType;
     }
@@ -211,8 +215,16 @@
       return oldType;
     }
 
-    boolean hasBeenChangedToReturnVoid(DexItemFactory dexItemFactory) {
-      return newType == dexItemFactory.voidType;
+    public SingleValue getSingleValue() {
+      return singleValue;
+    }
+
+    boolean hasBeenChangedToReturnVoid() {
+      return newType.isVoidType();
+    }
+
+    public boolean hasSingleValue() {
+      return singleValue != null;
     }
 
     @Override
@@ -231,9 +243,13 @@
         return info;
       }
       assert info.isRewrittenTypeInfo();
-      RewrittenTypeInfo rewrittenTypeInfo = info.asRewrittenTypeInfo();
-      assert newType == rewrittenTypeInfo.oldType;
-      return new RewrittenTypeInfo(oldType, rewrittenTypeInfo.newType);
+      return combine(info.asRewrittenTypeInfo());
+    }
+
+    public RewrittenTypeInfo combine(RewrittenTypeInfo other) {
+      assert !getNewType().isVoidType();
+      assert getNewType() == other.getOldType();
+      return new RewrittenTypeInfo(getOldType(), other.getNewType(), other.getSingleValue());
     }
 
     @Override
@@ -242,12 +258,14 @@
         return false;
       }
       RewrittenTypeInfo other = (RewrittenTypeInfo) obj;
-      return oldType == other.oldType && newType == other.newType;
+      return oldType == other.oldType
+          && newType == other.newType
+          && Objects.equals(singleValue, other.singleValue);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(oldType, newType);
+      return Objects.hash(oldType, newType, singleValue);
     }
 
     public boolean verifyIsDueToUnboxing(DexItemFactory dexItemFactory) {
@@ -549,18 +567,6 @@
         : new RewrittenPrototypeDescription(extraParameters, rewrittenReturnInfo, argumentsInfo);
   }
 
-  public static RewrittenPrototypeDescription createForUninstantiatedTypes(
-      DexMethod method,
-      AppView<AppInfoWithLiveness> appView,
-      ArgumentInfoCollection removedArgumentsInfo) {
-    DexType returnType = method.proto.returnType;
-    RewrittenTypeInfo returnInfo =
-        returnType.isAlwaysNull(appView)
-            ? RewrittenTypeInfo.toVoid(returnType, appView.dexItemFactory())
-            : null;
-    return create(Collections.emptyList(), returnInfo, removedArgumentsInfo);
-  }
-
   public static RewrittenPrototypeDescription createForRewrittenTypes(
       RewrittenTypeInfo returnInfo, ArgumentInfoCollection rewrittenArgumentsInfo) {
     return create(Collections.emptyList(), returnInfo, rewrittenArgumentsInfo);
@@ -622,9 +628,8 @@
     return extraParameters.size();
   }
 
-  public boolean hasBeenChangedToReturnVoid(DexItemFactory dexItemFactory) {
-    return rewrittenReturnInfo != null
-        && rewrittenReturnInfo.hasBeenChangedToReturnVoid(dexItemFactory);
+  public boolean hasBeenChangedToReturnVoid() {
+    return rewrittenReturnInfo != null && rewrittenReturnInfo.hasBeenChangedToReturnVoid();
   }
 
   public ArgumentInfoCollection getArgumentInfoCollection() {
@@ -654,12 +659,29 @@
    *
    * <p>Note that the current implementation always returns null at this point.
    */
-  public ConstInstruction getConstantReturn(IRCode code, Position position) {
-    ConstInstruction instruction = code.createConstNull();
+  public Instruction getConstantReturn(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      ProgramMethod method,
+      Position position,
+      TypeAndLocalInfoSupplier info) {
+    assert rewrittenReturnInfo != null;
+    assert rewrittenReturnInfo.hasSingleValue();
+    assert rewrittenReturnInfo.getSingleValue().isMaterializableInContext(appView, method);
+    Instruction instruction =
+        rewrittenReturnInfo.getSingleValue().createMaterializingInstruction(appView, code, info);
     instruction.setPosition(position);
     return instruction;
   }
 
+  public DexMethod rewriteMethod(ProgramMethod method, DexItemFactory dexItemFactory) {
+    if (isEmpty()) {
+      return method.getReference();
+    }
+    DexProto rewrittenProto = rewriteProto(method.getDefinition(), dexItemFactory);
+    return method.getReference().withProto(rewrittenProto, dexItemFactory);
+  }
+
   public DexProto rewriteProto(DexEncodedMethod encodedMethod, DexItemFactory dexItemFactory) {
     if (isEmpty()) {
       return encodedMethod.getReference().proto;
@@ -672,39 +694,13 @@
     return dexItemFactory.createProto(newReturnType, newParameters);
   }
 
-  public RewrittenPrototypeDescription withConstantReturn(
-      DexType oldReturnType, DexItemFactory dexItemFactory) {
-    assert rewrittenReturnInfo == null;
-    return !hasBeenChangedToReturnVoid(dexItemFactory)
-        ? new RewrittenPrototypeDescription(
-            extraParameters,
-            RewrittenTypeInfo.toVoid(oldReturnType, dexItemFactory),
-            argumentInfoCollection)
-        : this;
-  }
-
-  public RewrittenPrototypeDescription withRemovedArguments(ArgumentInfoCollection other) {
-    if (other.isEmpty()) {
+  public RewrittenPrototypeDescription withRewrittenReturnInfo(
+      RewrittenTypeInfo newRewrittenReturnInfo) {
+    if (Objects.equals(rewrittenReturnInfo, newRewrittenReturnInfo)) {
       return this;
     }
     return new RewrittenPrototypeDescription(
-        extraParameters, rewrittenReturnInfo, argumentInfoCollection.combine(other));
-  }
-
-  public RewrittenPrototypeDescription withRewrittenReturnInfo(
-      RewrittenTypeInfo rewrittenReturnInfo) {
-    if (rewrittenReturnInfo == null) {
-      return this;
-    }
-    if (!hasRewrittenReturnInfo()) {
-      return new RewrittenPrototypeDescription(
-          extraParameters, rewrittenReturnInfo, argumentInfoCollection);
-    }
-    throw new Unreachable();
-  }
-
-  public RewrittenPrototypeDescription withExtraUnusedNullParameter() {
-    return withExtraUnusedNullParameters(1);
+        extraParameters, newRewrittenReturnInfo, argumentInfoCollection);
   }
 
   public RewrittenPrototypeDescription withExtraUnusedNullParameters(
@@ -714,10 +710,6 @@
     return withExtraParameters(parameters);
   }
 
-  public RewrittenPrototypeDescription withExtraParameter(ExtraParameter parameter) {
-    return withExtraParameters(Collections.singletonList(parameter));
-  }
-
   public RewrittenPrototypeDescription withExtraParameters(List<ExtraParameter> parameters) {
     if (parameters.isEmpty()) {
       return this;
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
index 4c869ef..e25d391 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
@@ -162,7 +162,7 @@
       return bitSet;
     }
     int n = bitSet.length();
-    BitSet rewrittenNonNullParamOnNormalExits = new BitSet(n);
+    BitSet rewrittenBitSet = new BitSet(n);
     for (int argumentIndex = 0; argumentIndex < n; argumentIndex++) {
       if (!bitSet.get(argumentIndex)) {
         continue;
@@ -171,9 +171,8 @@
       if (argumentInfo.isRemovedArgumentInfo() || argumentInfo.isRewrittenTypeInfo()) {
         continue;
       }
-      rewrittenNonNullParamOnNormalExits.set(
-          getArgumentInfoCollection().getNewArgumentIndex(argumentIndex));
+      rewrittenBitSet.set(getArgumentInfoCollection().getNewArgumentIndex(argumentIndex));
     }
-    return rewrittenNonNullParamOnNormalExits.isEmpty() ? null : rewrittenNonNullParamOnNormalExits;
+    return rewrittenBitSet.isEmpty() ? null : rewrittenBitSet;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index eea0202..162ad98 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
 import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoCheckDiscard;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
 import com.android.tools.r8.horizontalclassmerging.policies.NoConstructorCollisions;
@@ -103,6 +104,7 @@
       ImmutableList.Builder<SingleClassPolicy> builder) {
     builder.add(
         new CheckSyntheticClasses(appView),
+        new NoCheckDiscard(appView),
         new NoKeepRules(appView),
         new NoClassInitializerWithObservableSideEffects());
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 3251db1..b3d27a5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.UseRegistry;
@@ -258,7 +258,7 @@
                     classInitializer.getOrigin(),
                     RewrittenPrototypeDescription.none());
 
-        DexType downcast = null;
+        DexProgramClass downcast = null;
         instructionIterator.previous();
         instructionIterator.inlineInvoke(
             appView, code, inliningIR, blockIterator, blocksToRemove, downcast);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoCheckDiscard.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoCheckDiscard.java
new file mode 100644
index 0000000..1266e1d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoCheckDiscard.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class NoCheckDiscard extends SingleClassPolicy {
+
+  private final KeepInfoCollection keepInfo;
+  private final InternalOptions options;
+
+  public NoCheckDiscard(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.keepInfo = appView.getKeepInfo();
+    this.options = appView.options();
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass clazz) {
+    return !keepInfo.getClassInfo(clazz).isCheckDiscardedEnabled(options);
+  }
+
+  @Override
+  public String getName() {
+    return "NoCheckDiscard";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index c9bcee0..911bddb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.classClassType;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
 
 public class SingleConstClassValue extends SingleConstValue {
 
@@ -104,7 +106,16 @@
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
       DexClass clazz = appView.definitionFor(type);
-      return clazz != null && clazz.isPublic() && clazz.isResolvable(appView);
+      if (clazz == null || !clazz.isPublic() || !clazz.isResolvable(appView)) {
+        return false;
+      }
+      ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+      SyntheticItems syntheticItems = appView.getSyntheticItems();
+      if (clazz.isProgramClass()
+          && classToFeatureSplitMap.isInFeature(clazz.asProgramClass(), syntheticItems)) {
+        return false;
+      }
+      return true;
     }
     assert baseType.isPrimitiveType();
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 530bfc4..a5d76c3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
 
 public abstract class SingleFieldValue extends SingleValue {
 
@@ -109,7 +111,16 @@
       assert false;
       return false;
     }
-    return holder.isPublic();
+    if (!holder.isPublic()) {
+      return false;
+    }
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    if (holder.isProgramClass()
+        && classToFeatureSplitMap.isInFeature(holder.asProgramClass(), syntheticItems)) {
+      return false;
+    }
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 2448b0c..62cbb04 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -743,7 +743,7 @@
       IRCode inlinee,
       ListIterator<BasicBlock> blocksIterator,
       Set<BasicBlock> blocksToRemove,
-      DexType downcast) {
+      DexProgramClass downcast) {
     assert blocksToRemove != null;
     ProgramMethod callerContext = code.context();
     ProgramMethod calleeContext = inlinee.context();
@@ -787,9 +787,9 @@
       //  instruction if the program still type checks without the cast.
       Value receiver = invoke.inValues().get(0);
       TypeElement castTypeLattice =
-          TypeElement.fromDexType(downcast, receiver.getType().nullability(), appView);
+          TypeElement.fromDexType(downcast.getType(), receiver.getType().nullability(), appView);
       SafeCheckCast castInstruction =
-          new SafeCheckCast(code.createValue(castTypeLattice), receiver, downcast);
+          new SafeCheckCast(code.createValue(castTypeLattice), receiver, downcast.getType());
       castInstruction.setPosition(invoke.getPosition());
 
       // Splice in the check cast operation.
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
new file mode 100644
index 0000000..b45feb0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.graph.DexField;
+
+public interface FieldPut {
+
+  DexField getField();
+
+  int getValueIndex();
+
+  Value value();
+
+  FieldInstruction asFieldInstruction();
+
+  boolean isInstancePut();
+
+  InstancePut asInstancePut();
+
+  boolean isStaticPut();
+
+  StaticPut asStaticPut();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 4e223924..f9bbcc8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -129,7 +130,7 @@
       IRCode inlinee,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
-      DexType downcast) {
+      DexProgramClass downcast) {
     throw new Unimplemented();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 20377f3..3dff277 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -32,7 +32,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Arrays;
 
-public class InstancePut extends FieldInstruction implements InstanceFieldInstruction {
+public class InstancePut extends FieldInstruction implements FieldPut, InstanceFieldInstruction {
 
   public InstancePut(DexField field, Value object, Value value) {
     this(field, object, value, false);
@@ -65,13 +65,18 @@
   }
 
   @Override
+  public int getValueIndex() {
+    return 1;
+  }
+
+  @Override
   public Value object() {
     return inValues.get(0);
   }
 
   @Override
   public Value value() {
-    return inValues.get(1);
+    return inValues.get(getValueIndex());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 0332c6e..85ac375 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -221,6 +221,7 @@
         newValue.addUser(this);
       }
     }
+    oldValue.removeUser(this);
   }
 
   public void replaceValue(int index, Value newValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 14a7d45..d08413d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -254,9 +255,9 @@
       IRCode inlinee,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
-      DexType downcast);
+      DexProgramClass downcast);
 
-  /** See {@link #inlineInvoke(AppView, IRCode, IRCode, ListIterator, Set, DexType)}. */
+  /** See {@link #inlineInvoke(AppView, IRCode, IRCode, ListIterator, Set, DexProgramClass)}. */
   default BasicBlock inlineInvoke(AppView<?> appView, IRCode code, IRCode inlinee) {
     Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     BasicBlock result = inlineInvoke(appView, code, inlinee, null, blocksToRemove, null);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 6c22403..92998f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -46,6 +47,29 @@
     this.method = target;
   }
 
+  public static InvokeMethod create(
+      Type type, DexMethod target, Value result, List<Value> arguments, boolean itf) {
+    switch (type) {
+      case DIRECT:
+        return new InvokeDirect(target, result, arguments, itf);
+      case INTERFACE:
+        return new InvokeInterface(target, result, arguments);
+      case STATIC:
+        return new InvokeStatic(target, result, arguments, itf);
+      case SUPER:
+        return new InvokeSuper(target, result, arguments, itf);
+      case VIRTUAL:
+        assert !itf;
+        return new InvokeVirtual(target, result, arguments);
+      case CUSTOM:
+      case MULTI_NEW_ARRAY:
+      case NEW_ARRAY:
+      case POLYMORPHIC:
+      default:
+        throw new Unreachable("Unexpected invoke type: " + type);
+    }
+  }
+
   public abstract boolean getInterfaceBit();
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 3e61866..b5aa980 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -146,7 +147,7 @@
       IRCode inlinee,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
-      DexType downcast) {
+      DexProgramClass downcast) {
     return currentBlockIterator.inlineInvoke(
         appView, code, inlinee, blockIterator, blocksToRemove, downcast);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 9b68a81..92eea52 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -30,7 +30,7 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
-public class StaticPut extends FieldInstruction implements StaticFieldInstruction {
+public class StaticPut extends FieldInstruction implements FieldPut, StaticFieldInstruction {
 
   public StaticPut(Value source, DexField field) {
     super(field, null, source);
@@ -47,9 +47,14 @@
   }
 
   @Override
+  public int getValueIndex() {
+    return 0;
+  }
+
+  @Override
   public Value value() {
     assert inValues.size() == 1;
-    return inValues.get(0);
+    return inValues.get(getValueIndex());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index 9bcee66..22b279b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -83,7 +83,7 @@
     nonTerminalFutures.add(
         ThreadUtils.processAsynchronously(
             () ->
-                converter.rewriteCode(
+                converter.rewriteNonDesugaredCode(
                     method,
                     eventConsumer,
                     OptimizationFeedbackIgnore.getInstance(),
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 0b2d838..d265b7b 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
@@ -1856,7 +1856,7 @@
   public void addReturn(int value) {
     DexType returnType = method.getDefinition().returnType();
     if (returnType.isVoidType()) {
-      assert prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory());
+      assert prototypeChanges.hasBeenChangedToReturnVoid();
       addReturn();
     } else {
       ValueTypeConstraint returnTypeConstraint =
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 ee74491..4f92d5b 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
@@ -547,7 +547,7 @@
     if (options.isGeneratingClassFiles()
         || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
       // We do not process in call graph order, so anything could be a leaf.
-      rewriteCode(
+      rewriteNonDesugaredCode(
           method,
           desugaringEventConsumer,
           simpleOptimizationFeedback,
@@ -1033,7 +1033,7 @@
     }
   }
 
-  Timing rewriteCode(
+  Timing rewriteNonDesugaredCode(
       ProgramMethod method,
       CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       OptimizationFeedback feedback,
@@ -1043,7 +1043,7 @@
         method.getOrigin(),
         new MethodPosition(method.getReference().asMethodReference()),
         () ->
-            rewriteCodeInternal(
+            rewriteNonDesugaredCodeInternal(
                 method,
                 desugaringEventConsumer,
                 feedback,
@@ -1056,20 +1056,36 @@
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
-    return rewriteCode(
-        method,
-        CfInstructionDesugaringEventConsumer.createForDesugaredCode(),
-        feedback,
-        methodProcessor,
-        methodProcessingContext);
+    return ExceptionUtils.withOriginAndPositionAttachmentHandler(
+        method.getOrigin(),
+        new MethodPosition(method.getReference().asMethodReference()),
+        () ->
+            rewriteDesugaredCodeInternal(
+                method, feedback, methodProcessor, methodProcessingContext));
   }
 
-  private Timing rewriteCodeInternal(
+  private Timing rewriteNonDesugaredCodeInternal(
       ProgramMethod method,
       CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
+    boolean didDesugar = desugar(method, desugaringEventConsumer, methodProcessingContext);
+    if (Log.ENABLED && didDesugar) {
+      Log.debug(
+          getClass(),
+          "Desugared code for %s:\n%s",
+          method.toSourceString(),
+          logCode(options, method.getDefinition()));
+    }
+    return rewriteDesugaredCodeInternal(method, feedback, methodProcessor, methodProcessingContext);
+  }
+
+  private Timing rewriteDesugaredCodeInternal(
+      ProgramMethod method,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
     if (options.verbose) {
       options.reporter.info(
           new StringDiagnostic("Processing: " + method.toSourceString()));
@@ -1081,14 +1097,6 @@
           method.toSourceString(),
           logCode(options, method.getDefinition()));
     }
-    boolean didDesugar = desugar(method, desugaringEventConsumer, methodProcessingContext);
-    if (Log.ENABLED && didDesugar) {
-      Log.debug(
-          getClass(),
-          "Desugared code for %s:\n%s",
-          method.toSourceString(),
-          logCode(options, method.getDefinition()));
-    }
     if (options.testing.hookInIrConversion != null) {
       options.testing.hookInIrConversion.run();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 0356381..d012cd6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
@@ -61,10 +62,10 @@
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstMethodHandle;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -97,12 +98,12 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiFunction;
@@ -122,7 +123,7 @@
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
-    if (insn.outValue() != null) {
+    if (insn.hasOutValue()) {
       TypeElement oldType = insn.getOutType();
       TypeElement newType = oldType.rewrittenWithLens(appView, appView.graphLens());
       return code.createValue(newType, insn.getLocalInfo());
@@ -149,7 +150,9 @@
     DexItemFactory factory = appView.dexItemFactory();
     // Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
     // we track all phi's that needs to be re-computed.
-    ListIterator<BasicBlock> blocks = code.listIterator();
+    BasicBlockIterator blocks = code.listIterator();
+    InterfaceTypeToClassTypeLensCodeRewriterHelper interfaceTypeToClassTypeRewriterHelper =
+        InterfaceTypeToClassTypeLensCodeRewriterHelper.create(appView, code, methodProcessor);
     boolean mayHaveUnreachableBlocks = false;
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
@@ -250,7 +253,11 @@
                 ArgumentInfoCollection argumentInfoCollection =
                     prototypeChanges.getArgumentInfoCollection();
                 if (argumentInfoCollection.isEmpty()) {
-                  newInValues = invoke.inValues();
+                  if (prototypeChanges.hasExtraParameters()) {
+                    newInValues = new ArrayList<>(invoke.inValues());
+                  } else {
+                    newInValues = invoke.inValues();
+                  }
                 } else {
                   if (argumentInfoCollection.hasRemovedArguments()) {
                     if (Log.ENABLED) {
@@ -282,11 +289,29 @@
                   }
                 }
 
-                ConstInstruction constantReturnMaterializingInstruction = null;
-                if (prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory())
-                    && invoke.outValue() != null) {
+                Instruction constantReturnMaterializingInstruction = null;
+                if (prototypeChanges.hasBeenChangedToReturnVoid() && invoke.hasOutValue()) {
+                  TypeAndLocalInfoSupplier typeAndLocalInfo =
+                      new TypeAndLocalInfoSupplier() {
+                        @Override
+                        public DebugLocalInfo getLocalInfo() {
+                          return invoke.getLocalInfo();
+                        }
+
+                        @Override
+                        public TypeElement getOutType() {
+                          return graphLens
+                              .lookupType(invokedMethod.getReturnType())
+                              .toTypeElement(appView);
+                        }
+                      };
                   constantReturnMaterializingInstruction =
-                      prototypeChanges.getConstantReturn(code, invoke.getPosition());
+                      prototypeChanges.getConstantReturn(
+                          appView.withLiveness(),
+                          code,
+                          method,
+                          invoke.getPosition(),
+                          typeAndLocalInfo);
                   if (invoke.outValue().hasLocalInfo()) {
                     constantReturnMaterializingInstruction
                         .outValue()
@@ -300,7 +325,7 @@
                 }
 
                 Value newOutValue =
-                    prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory())
+                    prototypeChanges.hasBeenChangedToReturnVoid()
                         ? null
                         : makeOutValue(invoke, code);
 
@@ -352,15 +377,17 @@
                 boolean isInterface =
                     getBooleanOrElse(
                         appView.definitionFor(actualTarget.holder), DexClass::isInterface, false);
-                Invoke newInvoke =
-                    Invoke.create(
-                        actualInvokeType,
-                        actualTarget,
-                        null,
-                        newOutValue,
-                        newInValues,
-                        isInterface);
+                InvokeMethod newInvoke =
+                    InvokeMethod.create(
+                        actualInvokeType, actualTarget, newOutValue, newInValues, isInterface);
+
                 iterator.replaceCurrentInstruction(newInvoke);
+
+                // Insert casts for the program to type check if interfaces has been vertically
+                // merged into their unique (non-interface) subclass. See also b/199561570.
+                interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
+                    invoke, newInvoke, lensLookup, blocks, block, iterator);
+
                 if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
                   affectedPhis.addAll(newOutValue.uniquePhiUsers());
                 }
@@ -379,7 +406,7 @@
 
                 DexType actualReturnType = actualTarget.proto.returnType;
                 DexType expectedReturnType = graphLens.lookupType(invokedMethod.proto.returnType);
-                if (newInvoke.outValue() != null && actualReturnType != expectedReturnType) {
+                if (newInvoke.hasOutValue() && actualReturnType != expectedReturnType) {
                   throw new Unreachable(
                       "Unexpected need to insert a cast. Possibly related to resolving"
                           + " b/79143143.\n"
@@ -444,8 +471,11 @@
               DexMethod replacementMethod =
                   graphLens.lookupPutFieldForMethod(rewrittenField, method.getReference());
               if (replacementMethod != null) {
-                iterator.replaceCurrentInstruction(
-                    new InvokeStatic(replacementMethod, null, instancePut.inValues()));
+                InvokeStatic replacement =
+                    new InvokeStatic(replacementMethod, null, instancePut.inValues());
+                iterator.replaceCurrentInstruction(replacement);
+                interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
+                    instancePut, replacement, blocks, block, iterator);
               } else if (rewrittenField != field) {
                 Value rewrittenValue =
                     rewriteValueIfDefault(
@@ -454,6 +484,8 @@
                     InstancePut.createPotentiallyInvalid(
                         rewrittenField, instancePut.object(), rewrittenValue);
                 iterator.replaceCurrentInstruction(newInstancePut);
+                interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
+                    instancePut, newInstancePut, blocks, block, iterator);
               }
             }
             break;
@@ -507,15 +539,21 @@
               DexField actualField = rewriteFieldReference(lookup, method);
               DexMethod replacementMethod =
                   graphLens.lookupPutFieldForMethod(actualField, method.getReference());
+
               if (replacementMethod != null) {
-                iterator.replaceCurrentInstruction(
-                    new InvokeStatic(
-                        replacementMethod, staticPut.outValue(), staticPut.inValues()));
+                InvokeStatic replacement =
+                    new InvokeStatic(replacementMethod, staticPut.outValue(), staticPut.inValues());
+                iterator.replaceCurrentInstruction(replacement);
+                interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
+                    staticPut, replacement, blocks, block, iterator);
               } else if (actualField != field) {
                 Value rewrittenValue =
                     rewriteValueIfDefault(
                         code, iterator, field.type, actualField.type, staticPut.value());
-                iterator.replaceCurrentInstruction(new StaticPut(rewrittenValue, actualField));
+                StaticPut replacement = new StaticPut(rewrittenValue, actualField);
+                iterator.replaceCurrentInstruction(replacement);
+                interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
+                    staticPut, replacement, blocks, block, iterator);
               }
             }
             break;
@@ -602,7 +640,7 @@
               if (ret.isReturnVoid()) {
                 break;
               }
-              DexType returnType = code.method().getReference().proto.returnType;
+              DexType returnType = code.context().getReturnType();
               Value retValue = ret.returnValue();
               DexType initialType =
                   retValue.getType().isPrimitiveType()
@@ -610,10 +648,18 @@
                       : factory.objectType; // Place holder, any reference type will do.
               Value rewrittenValue =
                   rewriteValueIfDefault(code, iterator, initialType, returnType, retValue);
+              Return rewrittenReturn;
               if (retValue != rewrittenValue) {
-                Return newReturn = new Return(rewrittenValue);
-                iterator.replaceCurrentInstruction(newReturn);
+                rewrittenReturn = new Return(rewrittenValue);
+                iterator.replaceCurrentInstruction(rewrittenReturn);
+              } else {
+                rewrittenReturn = ret;
               }
+
+              // Insert casts for the program to type check if interfaces has been vertically
+              // merged into their unique (non-interface) subclass. See also b/199561570.
+              interfaceTypeToClassTypeRewriterHelper.insertCastsForOperandsIfNeeded(
+                  rewrittenReturn, blocks, block, iterator);
             }
             break;
 
@@ -680,6 +726,10 @@
     if (!affectedPhis.isEmpty()) {
       new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
     }
+
+    // Finalize cast insertion.
+    interfaceTypeToClassTypeRewriterHelper.processWorklist();
+
     assert code.isConsistentSSABeforeTypesAreCorrect();
     assert code.hasNoMergedClasses(appView);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
index b0ea9ef..dbd3de8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.itf.ProgramEmulatedInterfaceSynthesizer;
-import com.android.tools.r8.ir.desugar.records.RecordRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -33,7 +33,7 @@
       }
       synthesizers.add(new DesugaredLibraryWrapperSynthesizer(appView));
     }
-    RecordRewriter recordRewriter = RecordRewriter.create(appView);
+    RecordDesugaring recordRewriter = RecordDesugaring.create(appView);
     if (recordRewriter != null) {
       synthesizers.add(recordRewriter);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 929b5a8..28334b1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -77,107 +77,6 @@
         companionMethodConsumer);
   }
 
-  // TODO(b/183998768): Remove this event consumer. It should be unneeded for R8 and for D8 the
-  //  desugaring of interface methods should be able to happen up front too avoiding the companion
-  //  callback on nest accessors.
-  public static CfInstructionDesugaringEventConsumer createForDesugaredCode() {
-    return new CfInstructionDesugaringEventConsumer() {
-
-      @Override
-      public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
-        // A synthesized nest based accessor may itself be defined on an interface, in which case
-        // desugaring the accessor will result in a rewrite to the companion method.
-      }
-
-      @Override
-      public void acceptClasspathEmulatedInterface(DexClasspathClass clazz) {
-        assert false;
-      }
-
-      @Override
-      public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
-        assert false;
-      }
-
-      @Override
-      public void acceptAPIConversion(ProgramMethod method) {
-        assert false;
-      }
-
-      @Override
-      public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
-        assert false;
-      }
-
-      @Override
-      public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
-        assert false;
-      }
-
-      @Override
-      public void acceptInvokeStaticInterfaceOutliningMethod(
-          ProgramMethod method, ProgramMethod context) {
-        assert false;
-      }
-
-      @Override
-      public void acceptRecordClass(DexProgramClass recordClass) {
-        assert false;
-      }
-
-      @Override
-      public void acceptRecordMethod(ProgramMethod method) {
-        assert false;
-      }
-
-      @Override
-      public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
-        assert false;
-      }
-
-      @Override
-      public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
-        assert false;
-      }
-
-      @Override
-      public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
-        assert false;
-      }
-
-      @Override
-      public void acceptConstantDynamicClass(
-          ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
-        assert false;
-      }
-
-      @Override
-      public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
-        assert false;
-      }
-
-      @Override
-      public void acceptNestFieldPutBridge(ProgramField target, ProgramMethod bridge) {
-        assert false;
-      }
-
-      @Override
-      public void acceptNestMethodBridge(ProgramMethod target, ProgramMethod bridge) {
-        assert false;
-      }
-
-      @Override
-      public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
-        assert false;
-      }
-
-      @Override
-      public void acceptCompanionClassClinit(ProgramMethod method) {
-        assert false;
-      }
-    };
-  }
-
   public static class D8CfInstructionDesugaringEventConsumer
       extends CfInstructionDesugaringEventConsumer {
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index 1e393aa..064e82d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterPostProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
-import com.android.tools.r8.ir.desugar.records.RecordRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -73,7 +73,7 @@
       if (apiCallbackSynthesizor != null) {
         desugarings.add(apiCallbackSynthesizor);
       }
-      RecordRewriter recordRewriter = RecordRewriter.create(appView);
+      RecordDesugaring recordRewriter = RecordDesugaring.create(appView);
       if (recordRewriter != null) {
         desugarings.add(recordRewriter);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 62b65d4..66d3a56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -25,7 +25,7 @@
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.desugar.records.RecordRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.twr.TwrInstructionDesugaring;
 import com.android.tools.r8.utils.IntBox;
@@ -48,7 +48,7 @@
   private final List<CfInstructionDesugaring> desugarings = new ArrayList<>();
 
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
-  private final RecordRewriter recordRewriter;
+  private final RecordDesugaring recordRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
@@ -125,7 +125,7 @@
     if (nestBasedAccessDesugaring != null) {
       desugarings.add(nestBasedAccessDesugaring);
     }
-    this.recordRewriter = RecordRewriter.create(appView);
+    this.recordRewriter = RecordDesugaring.create(appView);
     if (recordRewriter != null) {
       desugarings.add(recordRewriter);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 1c99088..6b76836 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -220,13 +220,7 @@
             method.getHolder().asClasspathOrLibraryClass(),
             appView,
             classBuilder -> {},
-            clazz -> {
-              // TODO(b/183998768): When interface method desugaring is cf to cf in R8, the
-              //  eventConsumer should always be non null.
-              if (eventConsumer != null) {
-                eventConsumer.acceptClasspathEmulatedInterface(clazz);
-              }
-            },
+            eventConsumer::acceptClasspathEmulatedInterface,
             methodBuilder ->
                 methodBuilder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
@@ -303,7 +297,7 @@
               .setAnnotations(definition.annotations())
               // Will be traced by the enqueuer.
               .disableAndroidApiLevelCheck()
-              // TODO(b/183998768): Should this not also be updating with a fake 'this'
+              // TODO(b/200938394): Should this not also be updating with a fake 'this'
               .setParameterAnnotationsList(definition.getParameterAnnotations())
               .setCode(ignored -> InvalidCode.getInstance());
         },
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
index 41668bd..434238a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -47,8 +47,6 @@
       InterfaceProcessingDesugaringEventConsumer eventConsumer,
       ExecutorService executorService)
       throws ExecutionException {
-    // TODO(b/183998768): Would be nice to use the ClassProcessing for the processing of classes,
-    //  and do here only the finalization.
     ThreadUtils.processItems(
         Iterables.filter(programClasses, (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
         clazz -> classProcessor.process(clazz, eventConsumer),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 6ab7f62..2152ef0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -365,7 +365,7 @@
           .addScanEffect(() -> leavingStaticInvokeToInterface(context))
           .build();
     }
-    // TODO(b/183998768): This should not be needed. Targeted synthetics should be in place.
+    // TODO(b/199135051): This should not be needed. Targeted synthetics should be in place.
     if (appView.getSyntheticItems().isPendingSynthetic(invoke.getMethod().getHolderType())) {
       // We did not create this code yet, but it will not require rewriting.
       return DesugarDescription.nothing();
@@ -551,7 +551,7 @@
                             directTarget.asProgramMethod());
                     companionMethod = companionMethodDefinition.getReference();
                   } else {
-                    // TODO(b/183998768): Why does this not create a stub on the class path?
+                    // TODO(b/200938617): Why does this not create a stub on the class path?
                     companionMethod = helper.privateAsMethodOfCompanionClass(directTarget);
                   }
                 } else {
@@ -716,11 +716,9 @@
                     methodProcessingContext,
                     dexItemFactory) -> {
                   DexClassAndMethod method = resolutionResult.getResolutionPair();
-                  // TODO(b/183998768): Why do this amend routine. We have done resolution, so would
-                  // that
-                  //  not be the correct target!? I think this is just legacy from before resolution
-                  // was
-                  //  implemented in full.
+                  // TODO(b/199135051): Why do this amend routine. We have done resolution, so would
+                  //  that not be the correct target!? I think this is just legacy from before
+                  //  resolution was implemented in full.
                   DexMethod amendedMethod =
                       amendDefaultMethod(context12.getHolder(), invokedMethod);
                   assert method.getReference() == amendedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
similarity index 98%
rename from src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
rename to src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index f508ca6..09319b6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -64,7 +64,7 @@
 import java.util.function.BiFunction;
 import org.objectweb.asm.Opcodes;
 
-public class RecordRewriter
+public class RecordDesugaring
     implements CfInstructionDesugaring, CfClassSynthesizerDesugaring, CfPostProcessingDesugaring {
 
   private final AppView<?> appView;
@@ -75,8 +75,8 @@
   public static final String GET_FIELDS_AS_OBJECTS_METHOD_NAME = "$record$getFieldsAsObjects";
   public static final String EQUALS_RECORD_METHOD_NAME = "$record$equals";
 
-  public static RecordRewriter create(AppView<?> appView) {
-    return appView.options().shouldDesugarRecords() ? new RecordRewriter(appView) : null;
+  public static RecordDesugaring create(AppView<?> appView) {
+    return appView.options().shouldDesugarRecords() ? new RecordDesugaring(appView) : null;
   }
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
@@ -85,7 +85,7 @@
     RecordEqualsCfCodeProvider.registerSynthesizedCodeReferences(factory);
   }
 
-  private RecordRewriter(AppView<?> appView) {
+  private RecordDesugaring(AppView<?> appView) {
     this.appView = appView;
     factory = appView.dexItemFactory();
     recordToStringHelperProto =
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 c6bb8a7..04ba47d 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
@@ -223,7 +223,7 @@
 
       // We can exploit that a catch handler must be dead if its guard is never instantiated
       // directly or indirectly.
-      if (appInfoWithLiveness != null && appView.options().enableUninstantiatedTypeOptimization) {
+      if (appInfoWithLiveness != null) {
         DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(guard));
         if (clazz != null && !appInfoWithLiveness.isInstantiatedDirectlyOrIndirectly(clazz)) {
           builder.add(new CatchHandler<>(guard, target));
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 0a42896..0332cce 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
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -703,7 +704,8 @@
   }
 
   @Override
-  public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
-    return null; // Maybe improve later.
+  public ClassTypeElement getReceiverTypeOrDefault(
+      InvokeMethod invoke, ClassTypeElement defaultValue) {
+    return defaultValue;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 7534cba..41b6fe6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -5,10 +5,10 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeDirect;
@@ -135,10 +135,14 @@
   public void markInlined(InlineeWithReason inlinee) {}
 
   @Override
-  public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
+  public ClassTypeElement getReceiverTypeOrDefault(
+      InvokeMethod invoke, ClassTypeElement defaultValue) {
     assert invoke.isInvokeMethodWithReceiver();
     Inliner.InliningInfo info = invokesToInline.get(invoke.asInvokeMethodWithReceiver());
     assert info != null;
-    return info.receiverType;
+    if (info.receiverClass != null) {
+      return info.receiverClass.getType().toTypeElement(appView).asClassType();
+    }
+    return defaultValue;
   }
 }
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 d36206d..7591f78 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -847,11 +848,11 @@
 
   public static class InliningInfo {
     public final ProgramMethod target;
-    public final DexType receiverType; // null, if unknown
+    public final DexProgramClass receiverClass; // null, if unknown
 
-    public InliningInfo(ProgramMethod target, DexType receiverType) {
+    public InliningInfo(ProgramMethod target, DexProgramClass receiverClass) {
       this.target = target;
-      this.receiverType = receiverType;
+      this.receiverClass = receiverClass;
     }
   }
 
@@ -1016,14 +1017,11 @@
             continue;
           }
 
-          DexType downcastTypeOrNull = getDowncastTypeIfNeeded(strategy, invoke, singleTarget);
-          if (downcastTypeOrNull != null) {
-            DexClass downcastClass = appView.definitionFor(downcastTypeOrNull, context);
-            if (downcastClass == null
-                || AccessControl.isClassAccessible(downcastClass, context, appView)
-                    .isPossiblyFalse()) {
-              continue;
-            }
+          DexProgramClass downcastClass = getDowncastTypeIfNeeded(strategy, invoke, singleTarget);
+          if (downcastClass != null
+              && AccessControl.isClassAccessible(downcastClass, context, appView)
+                  .isPossiblyFalse()) {
+            continue;
           }
 
           if (!inlineeStack.isEmpty()
@@ -1073,7 +1071,7 @@
           iterator.previous();
           strategy.markInlined(inlinee);
           iterator.inlineInvoke(
-              appView, code, inlinee.code, blockIterator, blocksToRemove, downcastTypeOrNull);
+              appView, code, inlinee.code, blockIterator, blocksToRemove, downcastClass);
 
           if (inlinee.reason == Reason.SINGLE_CALLER) {
             feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
@@ -1160,19 +1158,22 @@
     return false;
   }
 
-  private DexType getDowncastTypeIfNeeded(
+  private DexProgramClass getDowncastTypeIfNeeded(
       InliningStrategy strategy, InvokeMethod invoke, ProgramMethod target) {
     if (invoke.isInvokeMethodWithReceiver()) {
-      // If the invoke has a receiver but the actual type of the receiver is different
-      // from the computed target holder, inlining requires a downcast of the receiver.
-      DexType receiverType = strategy.getReceiverTypeIfKnown(invoke);
-      if (receiverType == null) {
-        // In case we don't know exact type of the receiver we use declared
-        // method holder as a fallback.
-        receiverType = invoke.getInvokedMethod().holder;
+      // If the invoke has a receiver but the actual type of the receiver is different from the
+      // computed target holder, inlining requires a downcast of the receiver. In case we don't know
+      // the exact type of the receiver we use the static type of the receiver.
+      Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+      if (!receiver.getType().isClassType()) {
+        return target.getHolder();
       }
-      if (!appView.appInfo().isSubtype(receiverType, target.getHolderType())) {
-        return target.getHolderType();
+
+      ClassTypeElement receiverType =
+          strategy.getReceiverTypeOrDefault(invoke, receiver.getType().asClassType());
+      ClassTypeElement targetType = target.getHolderType().toTypeElement(appView).asClassType();
+      if (!receiverType.lessThanOrEqualUpToNullability(targetType, appView)) {
+        return target.getHolder();
       }
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 1dc076f..b6d33ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeDirect;
@@ -49,5 +49,5 @@
 
   void ensureMethodProcessed(ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback);
 
-  DexType getReceiverTypeIfKnown(InvokeMethod invoke);
+  ClassTypeElement getReceiverTypeOrDefault(InvokeMethod invoke, ClassTypeElement defaultValue);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index 2b439a6..652c185 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -34,10 +34,6 @@
 
   private final Predicate<DexEncodedMethod> methodTester;
 
-  public MethodPoolCollection(AppView<AppInfoWithLiveness> appView) {
-    this(appView, appView.appInfo().computeSubtypingInfo());
-  }
-
   public MethodPoolCollection(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo) {
     this(appView, subtypingInfo, Predicates.alwaysTrue());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
deleted file mode 100644
index df7eeb3..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ /dev/null
@@ -1,331 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize;
-
-import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.ALLOW_ARGUMENT_REMOVAL;
-import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.DISALLOW_ARGUMENT_REMOVAL;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.NestedGraphLens;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
-import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-
-public class UninstantiatedTypeOptimization {
-
-  enum Strategy {
-    ALLOW_ARGUMENT_REMOVAL,
-    DISALLOW_ARGUMENT_REMOVAL
-  }
-
-  public static class UninstantiatedTypeOptimizationGraphLens extends NestedGraphLens {
-
-    private final Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod;
-
-    UninstantiatedTypeOptimizationGraphLens(
-        BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap,
-        Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod,
-        AppView<?> appView) {
-      super(appView, EMPTY_FIELD_MAP, methodMap, EMPTY_TYPE_MAP);
-      this.removedArgumentsInfoPerMethod = removedArgumentsInfoPerMethod;
-    }
-
-    @Override
-    protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-        RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-      DexMethod previous = internalGetPreviousMethodSignature(method);
-      if (previous == method) {
-        assert !removedArgumentsInfoPerMethod.containsKey(method);
-        return prototypeChanges;
-      }
-      if (method.getReturnType().isVoidType() && !previous.getReturnType().isVoidType()) {
-        prototypeChanges =
-            prototypeChanges.withConstantReturn(previous.getReturnType(), dexItemFactory());
-      }
-      return prototypeChanges.withRemovedArguments(
-          removedArgumentsInfoPerMethod.getOrDefault(method, ArgumentInfoCollection.empty()));
-    }
-  }
-
-  private static final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
-
-  private final AppView<AppInfoWithLiveness> appView;
-
-  public UninstantiatedTypeOptimization(AppView<AppInfoWithLiveness> appView) {
-    this.appView = appView;
-  }
-
-  public UninstantiatedTypeOptimization strenghtenOptimizationInfo() {
-    OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
-    AbstractValue nullValue = appView.abstractValueFactory().createSingleNumberValue(0);
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      clazz.forEachField(
-          field -> {
-            if (field.type().isAlwaysNull(appView)) {
-              feedback.recordFieldHasAbstractValue(field, appView, nullValue);
-            }
-          });
-      clazz.forEachMethod(
-          method -> {
-            if (method.returnType().isAlwaysNull(appView)) {
-              feedback.methodReturnsAbstractValue(method, appView, nullValue);
-            }
-          });
-    }
-    return this;
-  }
-
-  public UninstantiatedTypeOptimizationGraphLens run(
-      MethodPoolCollection methodPoolCollection, ExecutorService executorService, Timing timing) {
-    try {
-      methodPoolCollection.buildAll(executorService, timing);
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
-
-    Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods = new HashMap<>();
-    MutableBidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping =
-        new BidirectionalOneToOneHashMap<>();
-    Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod = new IdentityHashMap<>();
-
-    TopDownClassHierarchyTraversal.forProgramClasses(appView)
-        .visit(
-            appView.appInfo().classes(),
-            clazz ->
-                processClass(
-                    clazz,
-                    changedVirtualMethods,
-                    methodMapping.getForwardMap(),
-                    methodPoolCollection,
-                    removedArgumentsInfoPerMethod));
-
-    if (!methodMapping.isEmpty()) {
-      return new UninstantiatedTypeOptimizationGraphLens(
-          methodMapping, removedArgumentsInfoPerMethod, appView);
-    }
-    return null;
-  }
-
-  private void processClass(
-      DexProgramClass clazz,
-      Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods,
-      BiMap<DexMethod, DexMethod> methodMapping,
-      MethodPoolCollection methodPoolCollection,
-      Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod) {
-    MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
-
-    if (clazz.isInterface()) {
-      // Do not allow changing the prototype of methods that override an interface method.
-      // This achieved by faking that there is already a method with the given signature.
-      for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
-        RewrittenPrototypeDescription prototypeChanges =
-            RewrittenPrototypeDescription.createForUninstantiatedTypes(
-                virtualMethod.getReference(),
-                appView,
-                getRemovedArgumentsInfo(virtualMethod, ALLOW_ARGUMENT_REMOVAL));
-        if (!prototypeChanges.isEmpty()) {
-          DexMethod newMethod = getNewMethodSignature(virtualMethod, prototypeChanges);
-          Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
-          if (!methodPool.hasSeenDirectly(wrapper)) {
-            methodPool.seen(wrapper);
-          }
-        }
-      }
-      return;
-    }
-
-    Map<DexEncodedMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
-        new IdentityHashMap<>();
-    for (DexEncodedMethod directMethod : clazz.directMethods()) {
-      RewrittenPrototypeDescription prototypeChanges =
-          getPrototypeChanges(directMethod, ALLOW_ARGUMENT_REMOVAL);
-      if (!prototypeChanges.isEmpty()) {
-        prototypeChangesPerMethod.put(directMethod, prototypeChanges);
-      }
-    }
-
-    // Reserve all signatures which are known to not be touched below.
-    Set<Wrapper<DexMethod>> usedSignatures = new HashSet<>();
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (!prototypeChangesPerMethod.containsKey(method)) {
-        usedSignatures.add(equivalence.wrap(method.getReference()));
-      }
-    }
-
-    // Change the return type of direct methods that return an uninstantiated type to void.
-    clazz
-        .getMethodCollection()
-        .replaceDirectMethods(
-            encodedMethod -> {
-              DexMethod method = encodedMethod.getReference();
-              RewrittenPrototypeDescription prototypeChanges =
-                  prototypeChangesPerMethod.getOrDefault(
-                      encodedMethod, RewrittenPrototypeDescription.none());
-              ArgumentInfoCollection removedArgumentsInfo =
-                  prototypeChanges.getArgumentInfoCollection();
-              DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
-              if (newMethod != method) {
-                Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
-
-                // TODO(b/110806787): Can be extended to handle collisions by renaming the given
-                // method.
-                if (usedSignatures.add(wrapper)) {
-                  methodMapping.put(method, newMethod);
-                  if (removedArgumentsInfo.hasRemovedArguments()) {
-                    removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo);
-                  }
-                  return encodedMethod.toTypeSubstitutedMethod(
-                      newMethod,
-                      removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod));
-                }
-              }
-              return encodedMethod;
-            });
-
-    // Change the return type of virtual methods that return an uninstantiated type to void.
-    // This is done in two steps. First we change the return type of all methods that override
-    // a method whose return type has already been changed to void previously. Note that
-    // all supertypes of the current class are always visited prior to the current class.
-    // This is important to ensure that a method that used to override a method in its super
-    // class will continue to do so after this optimization.
-    clazz
-        .getMethodCollection()
-        .replaceVirtualMethods(
-            encodedMethod -> {
-              DexMethod method = encodedMethod.getReference();
-              RewrittenPrototypeDescription prototypeChanges =
-                  getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-              ArgumentInfoCollection removedArgumentsInfo =
-                  prototypeChanges.getArgumentInfoCollection();
-              DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
-              if (newMethod != method) {
-                Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
-
-                boolean isOverrideOfPreviouslyChangedMethodInSuperClass =
-                    changedVirtualMethods
-                        .getOrDefault(equivalence.wrap(method), ImmutableSet.of())
-                        .stream()
-                        .anyMatch(other -> appView.appInfo().isSubtype(clazz.type, other));
-                if (isOverrideOfPreviouslyChangedMethodInSuperClass) {
-                  assert methodPool.hasSeen(wrapper);
-
-                  boolean signatureIsAvailable = usedSignatures.add(wrapper);
-                  assert signatureIsAvailable;
-
-                  methodMapping.put(method, newMethod);
-                  return encodedMethod.toTypeSubstitutedMethod(
-                      newMethod,
-                      removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod));
-                }
-              }
-              return encodedMethod;
-            });
-    clazz
-        .getMethodCollection()
-        .replaceVirtualMethods(
-            encodedMethod -> {
-              DexMethod method = encodedMethod.getReference();
-              RewrittenPrototypeDescription prototypeChanges =
-                  getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-              ArgumentInfoCollection removedArgumentsInfo =
-                  prototypeChanges.getArgumentInfoCollection();
-              DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
-              if (newMethod != method) {
-                Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
-
-                // TODO(b/110806787): Can be extended to handle collisions by renaming the given
-                //  method. Note that this also requires renaming all of the methods that override
-                // this
-                //  method, though.
-                if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) {
-                  methodPool.seen(wrapper);
-
-                  methodMapping.put(method, newMethod);
-
-                  boolean added =
-                      changedVirtualMethods
-                          .computeIfAbsent(
-                              equivalence.wrap(method), key -> Sets.newIdentityHashSet())
-                          .add(clazz.type);
-                  assert added;
-
-                  return encodedMethod.toTypeSubstitutedMethod(
-                      newMethod,
-                      removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod));
-                }
-              }
-              return encodedMethod;
-            });
-  }
-
-  private RewrittenPrototypeDescription getPrototypeChanges(
-      DexEncodedMethod encodedMethod, Strategy strategy) {
-    if (ArgumentRemovalUtils.isPinned(encodedMethod, appView)
-        || appView.appInfo().isKeepConstantArgumentsMethod(encodedMethod.getReference())) {
-      return RewrittenPrototypeDescription.none();
-    }
-    return RewrittenPrototypeDescription.createForUninstantiatedTypes(
-        encodedMethod.getReference(), appView, getRemovedArgumentsInfo(encodedMethod, strategy));
-  }
-
-  private ArgumentInfoCollection getRemovedArgumentsInfo(
-      DexEncodedMethod encodedMethod, Strategy strategy) {
-    if (strategy == DISALLOW_ARGUMENT_REMOVAL) {
-      return ArgumentInfoCollection.empty();
-    }
-
-    ArgumentInfoCollection.Builder argInfosBuilder = ArgumentInfoCollection.builder();
-    DexProto proto = encodedMethod.getReference().proto;
-    int offset = encodedMethod.getFirstNonReceiverArgumentIndex();
-    for (int i = 0; i < proto.parameters.size(); ++i) {
-      DexType type = proto.parameters.values[i];
-      if (type.isAlwaysNull(appView)) {
-        RemovedArgumentInfo removedArg =
-            RemovedArgumentInfo.builder()
-                .setSingleValue(appView.abstractValueFactory().createNullValue())
-                .setType(type)
-                .build();
-        argInfosBuilder.addArgumentInfo(i + offset, removedArg);
-      }
-    }
-    return argInfosBuilder.build();
-  }
-
-  private DexMethod getNewMethodSignature(
-      DexEncodedMethod encodedMethod, RewrittenPrototypeDescription prototypeChanges) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexMethod method = encodedMethod.getReference();
-    DexProto newProto = prototypeChanges.rewriteProto(encodedMethod, dexItemFactory);
-
-    return dexItemFactory.createMethod(method.holder, newProto, method.name);
-  }
-}
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 1f4064f..5f05d8d 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
@@ -442,7 +442,7 @@
               throw new IllegalClassInlinerStateException();
             }
 
-            directMethodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass.type));
+            directMethodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass));
             break;
           }
         }
@@ -920,7 +920,7 @@
               .getParent();
     }
 
-    return new InliningInfo(singleTarget, eligibleClass.type);
+    return new InliningInfo(singleTarget, eligibleClass);
   }
 
   // An invoke is eligible for inlining in the following cases:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index da704c1..1fe7a27 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -12,6 +12,10 @@
 import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -28,7 +32,9 @@
 
 class EnumUnboxingLens extends NestedGraphLens {
 
+  private final AbstractValueFactory abstractValueFactory;
   private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
+  private final EnumDataMap unboxedEnums;
 
   EnumUnboxingLens(
       AppView<?> appView,
@@ -37,12 +43,36 @@
       Map<DexType, DexType> typeMap,
       Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod) {
     super(appView, fieldMap, methodMap::getRepresentativeValue, typeMap, methodMap);
+    assert !appView.unboxedEnums().isEmpty();
+    this.abstractValueFactory = appView.abstractValueFactory();
     this.prototypeChangesPerMethod = prototypeChangesPerMethod;
+    this.unboxedEnums = appView.unboxedEnums();
   }
 
   @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
       RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+    // Rewrite the single value of the given RewrittenPrototypeDescription if it is referring to an
+    // unboxed enum field.
+    if (prototypeChanges.hasRewrittenReturnInfo()) {
+      RewrittenTypeInfo rewrittenTypeInfo = prototypeChanges.getRewrittenReturnInfo();
+      if (rewrittenTypeInfo.hasSingleValue()) {
+        SingleValue singleValue = rewrittenTypeInfo.getSingleValue();
+        if (singleValue.isSingleFieldValue()) {
+          SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
+          if (unboxedEnums.hasUnboxedValueFor(singleFieldValue.getField())) {
+            prototypeChanges =
+                prototypeChanges.withRewrittenReturnInfo(
+                    new RewrittenTypeInfo(
+                        rewrittenTypeInfo.getOldType(),
+                        rewrittenTypeInfo.getNewType(),
+                        abstractValueFactory.createSingleNumberValue(
+                            unboxedEnums.getUnboxedValue(singleFieldValue.getField()))));
+          }
+        }
+      }
+    }
+
     // During the second IR processing enum unboxing is the only optimization rewriting
     // prototype description, if this does not hold, remove the assertion and merge
     // the two prototype changes.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index 2c93f77..b0b64ee 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -37,10 +39,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {
     if (singleTarget.getReference() == dexItemFactory.booleanMembers.booleanValue) {
       optimizeBooleanValue(code, instructionIterator, invoke);
     } else if (singleTarget.getReference() == dexItemFactory.booleanMembers.parseBoolean) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
index 5fb3d05..952d7cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ByteMethodOptimizer.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -33,10 +35,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {
     if (singleTarget.getReference() == dexItemFactory.byteMembers.byteValue) {
       optimizeByteValue(instructionIterator, invoke);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index 5ee7017..167a528 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -35,10 +37,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {
     if (singleTarget.getReference() == appView.dexItemFactory().enumMembers.valueOf
         && invoke.inValues().get(0).isConstClass()) {
       insertAssumeDynamicType(code, instructionIterator, invoke);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 1ce2493..935e869 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -115,48 +117,62 @@
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    InstructionListIterator instructionIterator = code.instructionListIterator();
-    Map<LibraryMethodModelCollection<?>, LibraryMethodModelCollection.State> optimizationStates =
-        new IdentityHashMap<>();
-    while (instructionIterator.hasNext()) {
-      Instruction instruction = instructionIterator.next();
-      if (!instruction.isInvokeMethod()) {
+    BasicBlockIterator blockIterator = code.listIterator();
+    Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      if (blocksToRemove.contains(block)) {
         continue;
       }
 
-      InvokeMethod invoke = instruction.asInvokeMethod();
-      DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
-      if (singleTarget == null) {
-        continue;
-      }
+      InstructionListIterator instructionIterator = block.listIterator(code);
+      Map<LibraryMethodModelCollection<?>, LibraryMethodModelCollection.State> optimizationStates =
+          new IdentityHashMap<>();
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (!instruction.isInvokeMethod()) {
+          continue;
+        }
 
-      LibraryMethodModelCollection<?> optimizer =
-          libraryMethodModelCollections.get(singleTarget.getHolderType());
-      if (optimizer == null) {
-        continue;
-      }
+        InvokeMethod invoke = instruction.asInvokeMethod();
+        DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+        if (singleTarget == null) {
+          continue;
+        }
 
-      if (invoke.hasUnusedOutValue()
-          && !singleTarget.getDefinition().isInstanceInitializer()
-          && !invoke.instructionMayHaveSideEffects(appView, code.context())) {
-        instructionIterator.removeOrReplaceByDebugLocalRead();
-        continue;
-      }
+        LibraryMethodModelCollection<?> optimizer =
+            libraryMethodModelCollections.get(singleTarget.getHolderType());
+        if (optimizer == null) {
+          continue;
+        }
 
-      LibraryMethodModelCollection.State optimizationState =
-          optimizationStates.computeIfAbsent(
-              optimizer,
-              libraryMethodModelCollection ->
-                  libraryMethodModelCollection.createInitialState(methodProcessor));
-      optimizer.optimize(
-          code,
-          instructionIterator,
-          invoke,
-          singleTarget,
-          affectedValues,
-          optimizationState,
-          methodProcessingContext);
+        if (invoke.hasUnusedOutValue()
+            && !singleTarget.getDefinition().isInstanceInitializer()
+            && !invoke.instructionMayHaveSideEffects(appView, code.context())) {
+          instructionIterator.removeOrReplaceByDebugLocalRead();
+          continue;
+        }
+
+        LibraryMethodModelCollection.State optimizationState =
+            optimizationStates.computeIfAbsent(
+                optimizer,
+                libraryMethodModelCollection ->
+                    libraryMethodModelCollection.createInitialState(methodProcessor));
+        optimizer.optimize(
+            code,
+            blockIterator,
+            instructionIterator,
+            invoke,
+            singleTarget,
+            affectedValues,
+            blocksToRemove,
+            optimizationState,
+            methodProcessingContext);
+      }
     }
+
+    code.removeBlocks(blocksToRemove);
+
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
index da38111..dbc5b0b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -34,28 +36,34 @@
    */
   void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
       T state,
       MethodProcessingContext methodProcessingContext);
 
   @SuppressWarnings("unchecked")
   default void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
       Object state,
       MethodProcessingContext methodProcessingContext) {
     optimize(
         code,
+        blockIterator,
         instructionIterator,
         invoke,
         singleTarget,
         affectedValues,
+        blocksToRemove,
         (T) state,
         methodProcessingContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
index aa93f3c..37ca53d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -102,10 +104,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {
     int maxRemovedAndroidLogLevel =
         appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel();
     if (singleTarget.getReference() == isLoggableMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
index 2eeb165..e545164 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -32,8 +34,10 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {}
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {}
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
index c325500..98e0014 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -30,10 +32,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {
     if (singleTarget.getReference() == dexItemFactory.objectMembers.getClass) {
       optimizeGetClass(instructionIterator, invoke);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index 1e518d6..7e55ce3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -44,10 +46,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {
     DexMethod singleTargetReference = singleTarget.getReference();
     switch (singleTargetReference.getName().byteAt(0)) {
       case 'e':
@@ -72,7 +76,14 @@
         break;
       case 'r':
         if (objectsMethods.isRequireNonNullMethod(singleTargetReference)) {
-          optimizeRequireNonNull(instructionIterator, invoke, affectedValues, singleTarget);
+          optimizeRequireNonNull(
+              code,
+              blockIterator,
+              instructionIterator,
+              invoke,
+              affectedValues,
+              blocksToRemove,
+              singleTarget);
         }
         break;
       case 't':
@@ -159,9 +170,12 @@
   }
 
   private void optimizeRequireNonNull(
+      IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
       DexClassAndMethod singleTarget) {
     if (invoke.hasOutValue() && invoke.outValue().hasLocalInfo()) {
       // Replacing the out-value with an in-value would change debug info.
@@ -176,7 +190,13 @@
       }
       instructionIterator.removeOrReplaceByDebugLocalRead();
     } else if (inValue.isAlwaysNull(appView)) {
-      if (singleTarget.getReference() == objectsMethods.requireNonNullElse) {
+      if (singleTarget.getReference() == objectsMethods.requireNonNull) {
+        // Optimize Objects.requireNonNull(null) into throw null.
+        if (appView.hasClassHierarchy()) {
+          instructionIterator.replaceCurrentInstructionWithThrowNull(
+              appView.withClassHierarchy(), code, blockIterator, blocksToRemove, affectedValues);
+        }
+      } else if (singleTarget.getReference() == objectsMethods.requireNonNullElse) {
         // Optimize Objects.requireNonNullElse(null, defaultObj) into defaultObj if defaultObj
         // is never null.
         if (invoke.getLastArgument().isNeverNull()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
index df15197..520c27a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -24,22 +26,33 @@
 
   public abstract void optimize(
       IRCode code,
-      InstructionListIterator instructionIterator,
-      InvokeMethod invoke,
-      DexClassAndMethod singleTarget,
-      Set<Value> affectedValues);
-
-  @Override
-  public final void optimize(
-      IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove);
+
+  @Override
+  public final void optimize(
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexClassAndMethod singleTarget,
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
       State state,
       MethodProcessingContext methodProcessingContext) {
     assert state == null;
-    optimize(code, instructionIterator, invoke, singleTarget, affectedValues);
+    optimize(
+        code,
+        blockIterator,
+        instructionIterator,
+        invoke,
+        singleTarget,
+        affectedValues,
+        blocksToRemove);
   }
 
   static class State implements LibraryMethodModelCollection.State {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index 07ab344..8e03c61 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.graph.DexItemFactory.StringBuildingMethods;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -67,10 +69,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
       State state,
       MethodProcessingContext methodProcessingContext) {
     if (invoke.isInvokeMethodWithReceiver()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 8c62340..e5483db 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -40,10 +42,12 @@
   @Override
   public void optimize(
       IRCode code,
+      BasicBlockIterator blockIterator,
       InstructionListIterator instructionIterator,
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
-      Set<Value> affectedValues) {
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove) {
     DexMethod singleTargetReference = singleTarget.getReference();
     if (singleTargetReference == dexItemFactory.stringMembers.equals) {
       optimizeEquals(code, instructionIterator, invoke.asInvokeVirtual());
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 537d9af..827f316 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
@@ -1033,7 +1033,8 @@
       RegisterPositions freePositions, int registerConstraint) {
     int n = Math.min(maxRegisterNumber, registerConstraint);
     for (int register = 0; register <= n; ++register) {
-      if (freePositions.get(register) > 0) {
+      if (!freePositions.isBlocked(register)) {
+        assert freePositions.get(register) > 0;
         // If this register is free according to freePositions, then it should also be free
         // according to freeRegisters.
         boolean isMoveExceptionRegister =
@@ -1639,7 +1640,7 @@
     }
 
     // Set all free positions for possible registers to max integer.
-    RegisterPositions freePositions = new RegisterPositions(registerConstraint + 1);
+    RegisterPositions freePositions = new RegisterPositionsImpl(registerConstraint + 1);
 
     if ((options().debug || code.method().getOptimizationInfo().isReachabilitySensitive())
         && !code.method().accessFlags.isStatic()) {
@@ -1648,7 +1649,7 @@
       // the input register.
       assert numberOfArgumentRegisters > 0;
       assert firstArgumentValue != null && firstArgumentValue.requiredRegisters() == 1;
-      freePositions.set(0, 0);
+      freePositions.setBlocked(0);
     }
 
     if (mode == ArgumentReuseMode.ALLOW_ARGUMENT_REUSE_U8BIT
@@ -1656,7 +1657,7 @@
       // Argument reuse is not allowed and we block all the argument registers so that
       // arguments are never free.
       for (int i = 0; i < numberOfArgumentRegisters && i <= registerConstraint; i++) {
-        freePositions.set(i, 0);
+        freePositions.setBlocked(i);
       }
     }
 
@@ -1667,7 +1668,7 @@
     if (overlapsMoveExceptionInterval(unhandledInterval)) {
       int moveExceptionRegister = getMoveExceptionRegister();
       if (moveExceptionRegister <= registerConstraint) {
-        freePositions.set(moveExceptionRegister, 0);
+        freePositions.setBlocked(moveExceptionRegister);
       }
     }
 
@@ -1677,7 +1678,7 @@
       if (activeRegister <= registerConstraint) {
         for (int i = 0; i < intervals.requiredRegisters(); i++) {
           if (activeRegister + i <= registerConstraint) {
-            freePositions.set(activeRegister + i, 0);
+            freePositions.setBlocked(activeRegister + i);
           }
         }
       }
@@ -1691,13 +1692,13 @@
         int nextOverlap = unhandledInterval.nextOverlap(intervals);
         for (int i = 0; i < intervals.requiredRegisters(); i++) {
           int register = inactiveRegister + i;
-          if (register <= registerConstraint) {
+          if (register <= registerConstraint && !freePositions.isBlocked(register)) {
             int unhandledStart = toInstructionPosition(unhandledInterval.getStart());
             if (nextOverlap == unhandledStart) {
               // Don't use the register for an inactive interval that is only free until the next
               // instruction. We can get into this situation when unhandledInterval starts at a
               // gap position.
-              freePositions.set(register, 0);
+              freePositions.setBlocked(register);
             } else {
               if (nextOverlap < freePositions.get(register)) {
                 freePositions.set(register, nextOverlap, intervals);
@@ -1717,8 +1718,9 @@
 
     // Get the register (pair) that is free the longest. That is the register with the largest
     // free position.
-    int candidate = getLargestValidCandidate(
-        unhandledInterval, registerConstraint, needsRegisterPair, freePositions, Type.ANY);
+    int candidate =
+        getLargestValidCandidate(
+            unhandledInterval, registerConstraint, needsRegisterPair, freePositions, Type.ANY);
 
     // It is not always possible to find a largest valid candidate. If none of the usable register
     // are free we typically get the last candidate. However, if that candidate has to be
@@ -1742,7 +1744,7 @@
       }
       // If the first use for these intervals is unconstrained, just spill this interval instead
       // of finding another candidate to spill via allocateBlockedRegister.
-      if (!unhandledInterval.getUses().first().hasConstraint()) {
+      if (!unhandledInterval.hasUses() || !unhandledInterval.getUses().first().hasConstraint()) {
         int nextConstrainedPosition = unhandledInterval.firstUseWithConstraint().getPosition();
         int register = getSpillRegister(unhandledInterval, null);
         LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
@@ -1835,6 +1837,9 @@
       return false;
     }
     if (register + (needsRegisterPair ? 1 : 0) <= registerConstraint) {
+      if (freePositions.isBlocked(register, needsRegisterPair)) {
+        return false;
+      }
       int freePosition = freePositions.get(register);
       if (needsRegisterPair) {
         freePosition = Math.min(freePosition, freePositions.get(register + 1));
@@ -1907,6 +1912,7 @@
   }
 
   private int getLargestCandidate(
+      LiveIntervals unhandledInterval,
       int registerConstraint,
       RegisterPositions freePositions,
       boolean needsRegisterPair,
@@ -1915,10 +1921,10 @@
     int largest = -1;
 
     for (int i = 0; i <= registerConstraint; i++) {
-      if (!freePositions.hasType(i, type)) {
+      if (freePositions.isBlocked(i, needsRegisterPair) || !freePositions.hasType(i, type)) {
         continue;
       }
-      int freePosition = freePositions.get(i);
+      int usePosition = freePositions.get(i);
       if (needsRegisterPair) {
         if (i == numberOfArgumentRegisters - 1) {
           // The last register of the method is |i|, so we cannot use the pair (|i|, |i+1|).
@@ -1927,11 +1933,16 @@
         if (i >= registerConstraint) {
           break;
         }
-        freePosition = Math.min(freePosition, freePositions.get(i + 1));
+        usePosition = Math.min(usePosition, freePositions.get(i + 1));
       }
-      if (freePosition > largest) {
+      if (unhandledInterval.hasUses() && usePosition == unhandledInterval.getFirstUse()) {
+        // This register has a use at the same instruction as the value we are allocation a register
+        // for. Find another register.
+        continue;
+      }
+      if (usePosition > largest) {
         candidate = i;
-        largest = freePosition;
+        largest = usePosition;
         if (largest == Integer.MAX_VALUE) {
           break;
         }
@@ -1943,44 +1954,87 @@
   private int handleWorkaround(
       Predicate<LiveIntervals> workaroundNeeded,
       BiPredicate<LiveIntervals, Integer> workaroundNeededForCandidate,
-      int candidate, LiveIntervals unhandledInterval, int registerConstraint,
-      boolean needsRegisterPair, RegisterPositions freePositions, RegisterPositions.Type type) {
+      int candidate,
+      LiveIntervals unhandledInterval,
+      int registerConstraint,
+      boolean needsRegisterPair,
+      RegisterPositionsWithExtraBlockedRegisters freePositions,
+      RegisterPositions.Type type) {
     if (workaroundNeeded.test(unhandledInterval)) {
       int lastCandidate = candidate;
       while (workaroundNeededForCandidate.test(unhandledInterval, candidate)) {
         // Make the unusable register unavailable for allocation and try again.
-        freePositions.set(candidate, 0);
-        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
+        freePositions.setBlockedTemporarily(candidate);
+        candidate =
+            getLargestCandidate(
+                unhandledInterval, registerConstraint, freePositions, needsRegisterPair, type);
         // If there are only invalid candidates of the give type we will end up with the same
         // candidate returned again once we have tried them all. In that case we didn't find a
         // valid register candidate and we need to broaden the search to other types.
         if (lastCandidate == candidate) {
+          assert false
+              : "Unexpected attempt to take blocked register "
+                  + candidate
+                  + " in "
+                  + code.context().toSourceString();
           return REGISTER_CANDIDATE_NOT_FOUND;
         }
+        // If we did not find a valid register, then give up, and broaden the search to other types.
+        if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
+          return candidate;
+        }
         lastCandidate = candidate;
       }
     }
     return candidate;
   }
 
-  private int getLargestValidCandidate(LiveIntervals unhandledInterval, int registerConstraint,
-      boolean needsRegisterPair, RegisterPositions freePositions, RegisterPositions.Type type) {
-    int candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
+  private int getLargestValidCandidate(
+      LiveIntervals unhandledInterval,
+      int registerConstraint,
+      boolean needsRegisterPair,
+      RegisterPositions usePositions,
+      RegisterPositions.Type type) {
+    int candidate =
+        getLargestCandidate(
+            unhandledInterval, registerConstraint, usePositions, needsRegisterPair, type);
     if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
       return candidate;
     }
-    candidate = handleWorkaround(
-        this::needsLongResultOverlappingLongOperandsWorkaround,
-        this::isLongResultOverlappingLongOperands,
-        candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
-    candidate = handleWorkaround(
-        this::needsSingleResultOverlappingLongOperandsWorkaround,
-        this::isSingleResultOverlappingLongOperands,
-        candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
-    candidate = handleWorkaround(
-        this::needsArrayGetWideWorkaround,
-        this::isArrayGetArrayRegister,
-        candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
+    // Wrap the use positions such that registers blocked by the workarounds are only blocked until
+    // the end of this method.
+    RegisterPositionsWithExtraBlockedRegisters usePositionsWrapper =
+        new RegisterPositionsWithExtraBlockedRegisters(usePositions);
+    candidate =
+        handleWorkaround(
+            this::needsLongResultOverlappingLongOperandsWorkaround,
+            this::isLongResultOverlappingLongOperands,
+            candidate,
+            unhandledInterval,
+            registerConstraint,
+            needsRegisterPair,
+            usePositionsWrapper,
+            type);
+    candidate =
+        handleWorkaround(
+            this::needsSingleResultOverlappingLongOperandsWorkaround,
+            this::isSingleResultOverlappingLongOperands,
+            candidate,
+            unhandledInterval,
+            registerConstraint,
+            needsRegisterPair,
+            usePositionsWrapper,
+            type);
+    candidate =
+        handleWorkaround(
+            this::needsArrayGetWideWorkaround,
+            this::isArrayGetArrayRegister,
+            candidate,
+            unhandledInterval,
+            registerConstraint,
+            needsRegisterPair,
+            usePositionsWrapper,
+            type);
     return candidate;
   }
 
@@ -1992,8 +2046,8 @@
     }
 
     // Initialize all candidate registers to Integer.MAX_VALUE.
-    RegisterPositions usePositions = new RegisterPositions(registerConstraint + 1);
-    RegisterPositions blockedPositions = new RegisterPositions(registerConstraint + 1);
+    RegisterPositions usePositions = new RegisterPositionsImpl(registerConstraint + 1);
+    RegisterPositions blockedPositions = new RegisterPositionsImpl(registerConstraint + 1);
 
     // Compute next use location for all currently active registers.
     for (LiveIntervals intervals : active) {
@@ -2027,12 +2081,12 @@
     // Disallow the reuse of argument registers by always treating them as being used
     // at instruction number 0.
     for (int i = 0; i < numberOfArgumentRegisters; i++) {
-      usePositions.set(i, 0);
+      usePositions.setBlocked(i);
     }
 
     // Disallow reuse of the move exception register if we have reserved one.
     if (overlapsMoveExceptionInterval(unhandledInterval)) {
-      usePositions.set(getMoveExceptionRegister(), 0);
+      usePositions.setBlocked(getMoveExceptionRegister());
     }
 
     // Treat active and inactive linked argument intervals as pinned. They cannot be given another
@@ -2046,12 +2100,18 @@
     boolean needsRegisterPair = unhandledInterval.getType().isWide();
 
     // First look for a candidate that can be rematerialized.
-    int candidate = getLargestValidCandidate(unhandledInterval, registerConstraint,
-        needsRegisterPair, usePositions, Type.CONST_NUMBER);
+    int candidate =
+        getLargestValidCandidate(
+            unhandledInterval,
+            registerConstraint,
+            needsRegisterPair,
+            usePositions,
+            Type.CONST_NUMBER);
     if (candidate != Integer.MAX_VALUE) {
       // Look for a non-const, non-monitor candidate.
-      int otherCandidate = getLargestValidCandidate(
-          unhandledInterval, registerConstraint, needsRegisterPair, usePositions, Type.OTHER);
+      int otherCandidate =
+          getLargestValidCandidate(
+              unhandledInterval, registerConstraint, needsRegisterPair, usePositions, Type.OTHER);
       if (otherCandidate == Integer.MAX_VALUE || candidate == REGISTER_CANDIDATE_NOT_FOUND) {
         candidate = otherCandidate;
       } else {
@@ -2063,6 +2123,7 @@
           candidate = otherCandidate;
         }
       }
+
       // If looking at constants and non-monitor registers did not find a valid spill candidate
       // we allow ourselves to look at monitor spill candidates as well. Registers holding objects
       // used as monitors should not be spilled if we can avoid it. Spilling them can lead
@@ -2343,7 +2404,8 @@
                 blockedPositions.set(register + i, firstUse, other);
                 // If we start blocking registers other than linked arguments, we might need to
                 // explicitly update the use positions as well as blocked positions.
-                assert usePositions.get(register + i) <= blockedPositions.get(register + i);
+                assert usePositions.isBlocked(register + i)
+                    || usePositions.get(register + i) <= blockedPositions.get(register + i);
               }
             }
           }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 0c5705c..8b81c83 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -389,6 +389,10 @@
     return Integer.MAX_VALUE;
   }
 
+  public boolean hasUses() {
+    return !uses.isEmpty();
+  }
+
   public int getFirstUse() {
     return uses.first().getPosition();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java
index eff8bea..caafb2e 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java
@@ -3,99 +3,37 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-import com.android.tools.r8.errors.Unreachable;
-import java.util.Arrays;
-import java.util.BitSet;
-
 /**
  * Simple mapping from a register to an int value.
- * <p>
- * The backing for the mapping grows as needed up to a given limit. If no mapping exists for
- * a register number the value is assumed to be Integer.MAX_VALUE.
+ *
+ * <p>The backing for the mapping grows as needed up to a given limit. If no mapping exists for a
+ * register number the value is assumed to be Integer.MAX_VALUE.
  */
+public abstract class RegisterPositions {
 
-public class RegisterPositions {
+  enum Type {
+    MONITOR,
+    CONST_NUMBER,
+    OTHER,
+    ANY
+  }
 
-  enum Type { MONITOR, CONST_NUMBER, OTHER, ANY }
+  public abstract boolean hasType(int index, Type type);
 
-  private static final int INITIAL_SIZE = 16;
-  private final int limit;
-  private int[] backing;
-  private final BitSet registerHoldsConstant;
-  private final BitSet registerHoldsMonitor;
-  private final BitSet registerHoldsNewStringInstanceDisallowingSpilling;
+  public abstract void set(int index, int value, LiveIntervals intervals);
 
-  public RegisterPositions(int limit) {
-    this.limit = limit;
-    backing = new int[INITIAL_SIZE];
-    for (int i = 0; i < INITIAL_SIZE; i++) {
-      backing[i] = Integer.MAX_VALUE;
+  public abstract int get(int index);
+
+  public abstract int getLimit();
+
+  public abstract void setBlocked(int index);
+
+  public abstract boolean isBlocked(int index);
+
+  public final boolean isBlocked(int index, boolean isWide) {
+    if (isBlocked(index)) {
+      return true;
     }
-    registerHoldsConstant = new BitSet(limit);
-    registerHoldsMonitor = new BitSet(limit);
-    registerHoldsNewStringInstanceDisallowingSpilling = new BitSet(limit);
-  }
-
-  public boolean hasType(int index, Type type) {
-    switch (type) {
-      case MONITOR:
-        return holdsMonitor(index);
-      case CONST_NUMBER:
-        return holdsConstant(index);
-      case OTHER:
-        return !holdsMonitor(index)
-            && !holdsConstant(index)
-            && !holdsNewStringInstanceDisallowingSpilling(index);
-      case ANY:
-        return true;
-      default:
-        throw new Unreachable("Unexpected register position type: " + type);
-    }
-  }
-
-  private boolean holdsConstant(int index) {
-    return registerHoldsConstant.get(index);
-  }
-
-  private boolean holdsMonitor(int index) { return registerHoldsMonitor.get(index); }
-
-  private boolean holdsNewStringInstanceDisallowingSpilling(int index) {
-    return registerHoldsNewStringInstanceDisallowingSpilling.get(index);
-  }
-
-  public void set(int index, int value) {
-    if (index >= backing.length) {
-      grow(index + 1);
-    }
-    backing[index] = value;
-  }
-
-  public void set(int index, int value, LiveIntervals intervals) {
-    set(index, value);
-    registerHoldsConstant.set(index, intervals.isConstantNumberInterval());
-    registerHoldsMonitor.set(index, intervals.usedInMonitorOperation());
-    registerHoldsNewStringInstanceDisallowingSpilling.set(
-        index, intervals.isNewStringInstanceDisallowingSpilling());
-  }
-
-  public int get(int index) {
-    if (index < backing.length) {
-      return backing[index];
-    }
-    assert index < limit;
-    return Integer.MAX_VALUE;
-  }
-
-  public void grow(int minSize) {
-    int size = backing.length;
-    while (size < minSize) {
-      size *= 2;
-    }
-    size = Math.min(size, limit);
-    int oldSize = backing.length;
-    backing = Arrays.copyOf(backing, size);
-    for (int i = oldSize; i < size; i++) {
-      backing[i] = Integer.MAX_VALUE;
-    }
+    return isWide && isBlocked(index + 1);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositionsImpl.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositionsImpl.java
new file mode 100644
index 0000000..0f11ebf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositionsImpl.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.regalloc;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.Arrays;
+import java.util.BitSet;
+
+public class RegisterPositionsImpl extends RegisterPositions {
+
+  private static final int INITIAL_SIZE = 16;
+  private final int limit;
+  private int[] backing;
+  private final BitSet registerHoldsConstant;
+  private final BitSet registerHoldsMonitor;
+  private final BitSet registerHoldsNewStringInstanceDisallowingSpilling;
+  private final BitSet blockedRegisters;
+
+  public RegisterPositionsImpl(int limit) {
+    this.limit = limit;
+    backing = new int[INITIAL_SIZE];
+    for (int i = 0; i < INITIAL_SIZE; i++) {
+      backing[i] = Integer.MAX_VALUE;
+    }
+    registerHoldsConstant = new BitSet(limit);
+    registerHoldsMonitor = new BitSet(limit);
+    registerHoldsNewStringInstanceDisallowingSpilling = new BitSet(limit);
+    blockedRegisters = new BitSet(limit);
+  }
+
+  @Override
+  public boolean hasType(int index, Type type) {
+    assert !isBlocked(index);
+    switch (type) {
+      case MONITOR:
+        return holdsMonitor(index);
+      case CONST_NUMBER:
+        return holdsConstant(index);
+      case OTHER:
+        return !holdsMonitor(index)
+            && !holdsConstant(index)
+            && !holdsNewStringInstanceDisallowingSpilling(index);
+      case ANY:
+        return true;
+      default:
+        throw new Unreachable("Unexpected register position type: " + type);
+    }
+  }
+
+  private boolean holdsConstant(int index) {
+    return registerHoldsConstant.get(index);
+  }
+
+  private boolean holdsMonitor(int index) {
+    return registerHoldsMonitor.get(index);
+  }
+
+  private boolean holdsNewStringInstanceDisallowingSpilling(int index) {
+    return registerHoldsNewStringInstanceDisallowingSpilling.get(index);
+  }
+
+  private void set(int index, int value) {
+    if (index >= backing.length) {
+      grow(index + 1);
+    }
+    backing[index] = value;
+  }
+
+  @Override
+  public void set(int index, int value, LiveIntervals intervals) {
+    set(index, value);
+    registerHoldsConstant.set(index, intervals.isConstantNumberInterval());
+    registerHoldsMonitor.set(index, intervals.usedInMonitorOperation());
+    registerHoldsNewStringInstanceDisallowingSpilling.set(
+        index, intervals.isNewStringInstanceDisallowingSpilling());
+  }
+
+  @Override
+  public int get(int index) {
+    assert !isBlocked(index);
+    if (index < backing.length) {
+      return backing[index];
+    }
+    assert index < limit;
+    return Integer.MAX_VALUE;
+  }
+
+  @Override
+  public int getLimit() {
+    return limit;
+  }
+
+  @Override
+  public void setBlocked(int index) {
+    blockedRegisters.set(index);
+  }
+
+  @Override
+  public boolean isBlocked(int index) {
+    return blockedRegisters.get(index);
+  }
+
+  private void grow(int minSize) {
+    int size = backing.length;
+    while (size < minSize) {
+      size *= 2;
+    }
+    size = Math.min(size, limit);
+    int oldSize = backing.length;
+    backing = Arrays.copyOf(backing, size);
+    for (int i = oldSize; i < size; i++) {
+      backing[i] = Integer.MAX_VALUE;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositionsWithExtraBlockedRegisters.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositionsWithExtraBlockedRegisters.java
new file mode 100644
index 0000000..16c7bb4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositionsWithExtraBlockedRegisters.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.regalloc;
+
+import java.util.BitSet;
+
+public class RegisterPositionsWithExtraBlockedRegisters extends RegisterPositions {
+
+  private final RegisterPositions positions;
+  private final BitSet extraBlockedRegisters;
+
+  public RegisterPositionsWithExtraBlockedRegisters(RegisterPositions positions) {
+    this.positions = positions;
+    this.extraBlockedRegisters = new BitSet(positions.getLimit());
+  }
+
+  @Override
+  public boolean hasType(int index, Type type) {
+    assert !isBlockedTemporarily(index);
+    return positions.hasType(index, type);
+  }
+
+  @Override
+  public void set(int index, int value, LiveIntervals intervals) {
+    positions.set(index, value, intervals);
+  }
+
+  @Override
+  public int get(int index) {
+    assert !isBlockedTemporarily(index);
+    return positions.get(index);
+  }
+
+  @Override
+  public int getLimit() {
+    return positions.getLimit();
+  }
+
+  @Override
+  public void setBlocked(int index) {
+    positions.setBlocked(index);
+  }
+
+  public void setBlockedTemporarily(int index) {
+    extraBlockedRegisters.set(index);
+  }
+
+  @Override
+  public boolean isBlocked(int index) {
+    return positions.isBlocked(index) || isBlockedTemporarily(index);
+  }
+
+  public boolean isBlockedTemporarily(int index) {
+    return extraBlockedRegisters.get(index);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index dcf4d30..c3677ba 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -132,7 +132,7 @@
 
   private void writeApplication(ClassFileConsumer consumer) {
     if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
-      marker.setPgMapId(proguardMapSupplier.writeProguardMap().get());
+      marker.setPgMapId(proguardMapSupplier.writeProguardMap().getId());
     }
     Optional<String> markerString =
         includeMarker(marker) ? Optional.of(marker.toString()) : Optional.empty();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 2d36290..7e7cff8 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
-import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -17,6 +16,7 @@
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 public class ProguardMapSupplier {
 
@@ -26,16 +26,29 @@
   public static final String MARKER_KEY_COMPILER_HASH = "compiler_hash";
   public static final String MARKER_KEY_MIN_API = "min_api";
   public static final String MARKER_KEY_PG_MAP_ID = "pg_map_id";
+  public static final String MARKER_KEY_PG_MAP_HASH = "pg_map_hash";
+  public static final String SHA_256_KEY = "SHA-256";
 
   public static int PG_MAP_ID_LENGTH = 7;
 
-  // Truncated murmur hash of the non-whitespace codepoints of the Proguard map (excluding the
-  // marker).
-  public static class ProguardMapId extends Box<String> {
+  // Hash of the Proguard map (excluding the header up to and including the hash marker).
+  public static class ProguardMapId {
+    private final String hash;
+
     private ProguardMapId(String id) {
-      super(id);
       assert id != null;
-      assert id.length() == PG_MAP_ID_LENGTH;
+      this.hash = id;
+    }
+
+    /** Truncated prefix of the content hash. */
+    // TODO(b/201269335): Update this to be a full "source-file marker".
+    public String getId() {
+      return hash.substring(0, PG_MAP_ID_LENGTH);
+    }
+
+    /** The actual content hash. */
+    public String getHash() {
+      return hash;
     }
   }
 
@@ -99,7 +112,6 @@
       builder.append(
           "# " + MARKER_KEY_COMPILER_HASH + ": " + VersionProperties.INSTANCE.getSha() + "\n");
     }
-    builder.append("# " + MARKER_KEY_PG_MAP_ID + ": " + id.get() + "\n");
     // Turn off linting of the mapping file in some build systems.
     builder.append("# common_typos_disable" + "\n");
     // Emit the R8 specific map-file version.
@@ -110,26 +122,31 @@
           .append(new MapVersionMappingInformation(mapVersion).serialize())
           .append("\n");
     }
+    builder.append("# " + MARKER_KEY_PG_MAP_ID + ": " + id.getId() + "\n");
+    // Place the map hash as the last header item. Everything past this line is the hashed content.
+    builder
+        .append("# ")
+        .append(MARKER_KEY_PG_MAP_HASH)
+        .append(": ")
+        .append(SHA_256_KEY)
+        .append(" ")
+        .append(id.getHash())
+        .append("\n");
     consumer.accept(builder.toString(), reporter);
   }
 
   static class ProguardMapIdBuilder implements ChainableStringConsumer {
 
-    private final Hasher hasher = Hashing.murmur3_32().newHasher();
+    private final Hasher hasher = Hashing.sha256().newHasher();
 
     @Override
     public ProguardMapIdBuilder accept(String string) {
-      for (int i = 0; i < string.length(); i++) {
-        char c = string.charAt(i);
-        if (!Character.isWhitespace(c)) {
-          hasher.putInt(c);
-        }
-      }
+      hasher.putString(string, StandardCharsets.UTF_8);
       return this;
     }
 
     public ProguardMapId build() {
-      return new ProguardMapId(hasher.hash().toString().substring(0, PG_MAP_ID_LENGTH));
+      return new ProguardMapId(hasher.hash().toString());
     }
   }
 
@@ -142,7 +159,7 @@
     }
   }
 
-  static class ProguardMapChecker implements StringConsumer {
+  public static class ProguardMapChecker implements StringConsumer {
 
     private final StringConsumer inner;
     private final StringBuilder contents = new StringBuilder();
@@ -164,7 +181,9 @@
     @Override
     public void finished(DiagnosticsHandler handler) {
       inner.finished(handler);
-      assert validateProguardMapParses(contents.toString());
+      String stringContent = contents.toString();
+      assert validateProguardMapParses(stringContent);
+      assert validateProguardMapHash(stringContent).isOk();
     }
 
     private static boolean validateProguardMapParses(String content) {
@@ -176,5 +195,78 @@
       }
       return true;
     }
+
+    public static class VerifyMappingFileHashResult {
+      private final boolean error;
+      private final String message;
+
+      public static VerifyMappingFileHashResult createOk() {
+        return new VerifyMappingFileHashResult(false, null);
+      }
+
+      public static VerifyMappingFileHashResult createInfo(String message) {
+        return new VerifyMappingFileHashResult(false, message);
+      }
+
+      public static VerifyMappingFileHashResult createError(String message) {
+        return new VerifyMappingFileHashResult(true, message);
+      }
+
+      private VerifyMappingFileHashResult(boolean error, String message) {
+        this.error = error;
+        this.message = message;
+      }
+
+      public boolean isOk() {
+        return !error && message == null;
+      }
+
+      public boolean isError() {
+        return error;
+      }
+
+      public String getMessage() {
+        assert message != null;
+        return message;
+      }
+    }
+
+    public static VerifyMappingFileHashResult validateProguardMapHash(String content) {
+      int lineEnd = -1;
+      while (true) {
+        int lineStart = lineEnd + 1;
+        lineEnd = content.indexOf('\n', lineStart);
+        if (lineEnd < 0) {
+          return VerifyMappingFileHashResult.createInfo("Failure to find map hash");
+        }
+        String line = content.substring(lineStart, lineEnd).trim();
+        if (line.isEmpty()) {
+          // Ignore empty lines in the header.
+          continue;
+        }
+        if (line.charAt(0) != '#') {
+          // At the first non-empty non-comment line we assume that the file has no hash marker.
+          return VerifyMappingFileHashResult.createInfo("Failure to find map hash in header");
+        }
+        String headerLine = line.substring(1).trim();
+        if (headerLine.startsWith(MARKER_KEY_PG_MAP_HASH)) {
+          int shaIndex = headerLine.indexOf(SHA_256_KEY + " ", MARKER_KEY_PG_MAP_HASH.length());
+          if (shaIndex < 0) {
+            return VerifyMappingFileHashResult.createError(
+                "Unknown map hash function: '" + headerLine + "'");
+          }
+          String headerHash = headerLine.substring(shaIndex + SHA_256_KEY.length()).trim();
+          // We are on the hash line. Everything past this line is the hashed content.
+          Hasher hasher = Hashing.sha256().newHasher();
+          String hashedContent = content.substring(lineEnd + 1);
+          hasher.putString(hashedContent, StandardCharsets.UTF_8);
+          String computedHash = hasher.hash().toString();
+          return headerHash.equals(computedHash)
+              ? VerifyMappingFileHashResult.createOk()
+              : VerifyMappingFileHashResult.createError(
+                  "Mismatching map hash: '" + headerHash + "' != '" + computedHash + "'");
+        }
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
index 8dccf93..cc8c795 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
@@ -159,8 +159,8 @@
 
     @Override
     public boolean evaluate(RetraceStackTraceContextImpl context) {
-      return context.getSeenException() != null
-          && context.getSeenException().getDescriptor().equals(descriptor);
+      return context.getThrownException() != null
+          && context.getThrownException().getDescriptor().equals(descriptor);
     }
 
     public static ThrowsCondition deserialize(String conditionString) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index c3fe41b..a0f67e0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -44,7 +44,12 @@
       assert !this.prototypeChanges.containsKey(method);
       return prototypeChanges;
     }
-    return prototypeChanges.combine(getPrototypeChanges(method));
+    RewrittenPrototypeDescription newPrototypeChanges =
+        prototypeChanges.combine(getPrototypeChanges(method));
+    assert previous.getReturnType().isVoidType()
+        || !method.getReturnType().isVoidType()
+        || newPrototypeChanges.hasRewrittenReturnInfo();
+    return newPrototypeChanges;
   }
 
   @Override
@@ -87,6 +92,9 @@
       if (!prototypeChangesForMethod.isEmpty()) {
         prototypeChanges.put(to, prototypeChangesForMethod);
       }
+      assert from.getReturnType().isVoidType()
+          || !to.getReturnType().isVoidType()
+          || prototypeChangesForMethod.hasRewrittenReturnInfo();
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index 68c7d12..eff9893 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -105,10 +105,15 @@
               clazz.forEachProgramMethodMatching(
                   DexEncodedMethod::hasCode,
                   method -> {
-                    AffectedMethodUseRegistry registry =
-                        new AffectedMethodUseRegistry(appView, graphLens);
-                    if (method.registerCodeReferencesWithResult(registry)) {
+                    if (graphLens.internalGetNextMethodSignature(method.getReference())
+                        != method.getReference()) {
                       methodsToReprocessInClass.add(method);
+                    } else {
+                      AffectedMethodUseRegistry registry =
+                          new AffectedMethodUseRegistry(appView, graphLens);
+                      if (method.registerCodeReferencesWithResult(registry)) {
+                        methodsToReprocessInClass.add(method);
+                      }
                     }
                   });
               return methodsToReprocessInClass;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index ae2a8aa..7118043 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeParameterState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeParameterState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
@@ -80,9 +82,6 @@
     //  instructions, build a flow graph where nodes are parameters and there is an edge from a
     //  parameter p1 to p2 if the value of p2 is at least the value of p1. Then propagate the
     //  collected argument information throughout the flow graph.
-    // TODO(b/190154391): If we learn that parameter p1 is constant, and that the enclosing method
-    //  returns p1 according to the optimization info, then update the optimization info to describe
-    //  that the method returns the constant.
     timing.begin("Propagate argument information for virtual methods");
     ThreadUtils.processItems(
         stronglyConnectedProgramComponents,
@@ -165,6 +164,7 @@
       methodState = MethodState.unknown();
     }
 
+    methodState = getMethodStateAfterUninstantiatedParameterRemoval(method, methodState);
     methodState = getMethodStateAfterUnusedParameterRemoval(method, methodState);
 
     if (methodState.isUnknown()) {
@@ -220,11 +220,54 @@
       return;
     }
 
+    ConcreteMonomorphicMethodState finalMethodState = widenedMethodState.asMonomorphic();
     method
         .getDefinition()
         .setCallSiteOptimizationInfo(
-            ConcreteCallSiteOptimizationInfo.fromMethodState(
-                appView, method, widenedMethodState.asMonomorphic()));
+            ConcreteCallSiteOptimizationInfo.fromMethodState(appView, method, finalMethodState));
+
+    // Strengthen the return value of the method if the method is known to return one of the
+    // arguments.
+    MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
+    if (optimizationInfo.returnsArgument()) {
+      ParameterState returnedArgumentState =
+          finalMethodState.getParameterState(optimizationInfo.getReturnedArgument());
+      OptimizationFeedback.getSimple()
+          .methodReturnsAbstractValue(
+              method.getDefinition(), appView, returnedArgumentState.getAbstractValue(appView));
+    }
+  }
+
+  private MethodState getMethodStateAfterUninstantiatedParameterRemoval(
+      ProgramMethod method, MethodState methodState) {
+    assert methodState.isMonomorphic() || methodState.isUnknown();
+    if (appView.appInfo().isKeepConstantArgumentsMethod(method)) {
+      return methodState;
+    }
+
+    int numberOfArguments = method.getDefinition().getNumberOfArguments();
+    List<ParameterState> parameterStates =
+        methodState.isMonomorphic()
+            ? methodState.asMonomorphic().getParameterStates()
+            : ListUtils.newInitializedArrayList(numberOfArguments, ParameterState.unknown());
+    List<ParameterState> narrowedParameterStates =
+        ListUtils.mapOrElse(
+            parameterStates,
+            (argumentIndex, parameterState) -> {
+              if (!method.getDefinition().isStatic() && argumentIndex == 0) {
+                return parameterState;
+              }
+              DexType argumentType = method.getArgumentType(argumentIndex);
+              if (!argumentType.isAlwaysNull(appView)) {
+                return parameterState;
+              }
+              return new ConcreteClassTypeParameterState(
+                  appView.abstractValueFactory().createNullValue(), DynamicType.definitelyNull());
+            },
+            null);
+    return narrowedParameterStates != null
+        ? new ConcreteMonomorphicMethodState(narrowedParameterStates)
+        : methodState;
   }
 
   private MethodState getMethodStateAfterUnusedParameterRemoval(
@@ -263,12 +306,16 @@
 
   private ParameterState getUnusedParameterState(DexType argumentType) {
     if (argumentType.isArrayType()) {
+      // Ensure argument removal by simulating that this unused parameter is the constant null.
       return new ConcreteArrayTypeParameterState(Nullability.definitelyNull());
     } else if (argumentType.isClassType()) {
+      // Ensure argument removal by simulating that this unused parameter is the constant null.
       return new ConcreteClassTypeParameterState(
           appView.abstractValueFactory().createNullValue(), DynamicType.definitelyNull());
     } else {
       assert argumentType.isPrimitiveType();
+      // Ensure argument removal by simulating that this unused parameter is the constant zero.
+      // Note that the same zero value is used for all primitive types.
       return new ConcretePrimitiveTypeParameterState(
           appView.abstractValueFactory().createZeroValue());
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 055013f..57611a3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -19,17 +19,22 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -52,6 +57,47 @@
 
 public class ArgumentPropagatorProgramOptimizer {
 
+  static class AllowedPrototypeChanges {
+
+    private static final AllowedPrototypeChanges EMPTY =
+        new AllowedPrototypeChanges(false, IntSets.EMPTY_SET);
+
+    boolean canRewriteToVoid;
+    IntSet removableParameterIndices;
+
+    AllowedPrototypeChanges(boolean canRewriteToVoid, IntSet removableParameterIndices) {
+      this.canRewriteToVoid = canRewriteToVoid;
+      this.removableParameterIndices = removableParameterIndices;
+    }
+
+    public static AllowedPrototypeChanges create(RewrittenPrototypeDescription prototypeChanges) {
+      return prototypeChanges.isEmpty()
+          ? empty()
+          : new AllowedPrototypeChanges(
+              prototypeChanges.hasBeenChangedToReturnVoid(),
+              prototypeChanges.getArgumentInfoCollection().getKeys());
+    }
+
+    public static AllowedPrototypeChanges empty() {
+      return EMPTY;
+    }
+
+    @Override
+    public int hashCode() {
+      return BooleanUtils.intValue(canRewriteToVoid) | (removableParameterIndices.hashCode() << 1);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      AllowedPrototypeChanges other = (AllowedPrototypeChanges) obj;
+      return canRewriteToVoid == other.canRewriteToVoid
+          && removableParameterIndices.equals(other.removableParameterIndices);
+    }
+  }
+
   private final AppView<AppInfoWithLiveness> appView;
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
 
@@ -117,17 +163,24 @@
     private final DexItemFactory dexItemFactory;
     private final InternalOptions options;
 
-    private final Map<DexMethodSignature, IntSet> removableVirtualMethodParameters =
-        new HashMap<>();
+    private final Map<DexMethodSignature, AllowedPrototypeChanges>
+        allowedPrototypeChangesForVirtualMethods = new HashMap<>();
 
-    // Reserved names, i.e., mappings from pairs (old method signature, number of removed arguments)
-    // to the new method signature for that method.
-    private final Map<DexMethodSignature, Map<IntSet, DexMethodSignature>> newMethodSignatures =
-        new HashMap<>();
+    private final ProgramMethodMap<SingleValue> returnValuesForVirtualMethods =
+        ProgramMethodMap.create();
+
+    // Reserved names, i.e., mappings from pairs (old method signature, prototype changes) to the
+    // new method signature for that method.
+    private final Map<DexMethodSignature, Map<AllowedPrototypeChanges, DexMethodSignature>>
+        newMethodSignatures = new HashMap<>();
+
+    // The method name suffix to start from when searching for a fresh method signature. Used to
+    // avoid searching from index 0 to a large number when searching for a fresh method signature.
+    private final Map<DexMethodSignature, IntBox> newMethodSignatureSuffixes = new HashMap<>();
 
     // Occupied method signatures (inverse of reserved names). Used to effectively check if a given
     // method signature is already reserved.
-    private final Map<DexMethodSignature, Pair<IntSet, DexMethodSignature>>
+    private final Map<DexMethodSignature, Pair<AllowedPrototypeChanges, DexMethodSignature>>
         occupiedMethodSignatures = new HashMap<>();
 
     public StronglyConnectedComponentOptimizer() {
@@ -160,7 +213,7 @@
       // To ensure that we preserve the overriding relationships between methods, we only remove a
       // constant or unused parameter from a virtual method when it can be removed from all other
       // virtual methods in the component with the same method signature.
-      computeRemovableVirtualMethodParameters(stronglyConnectedProgramClasses);
+      computePrototypeChangesForVirtualMethods(stronglyConnectedProgramClasses);
 
       // Build a graph lens while visiting the classes in the component.
       // TODO(b/190154391): Consider visiting the interfaces first, and then processing the
@@ -201,36 +254,37 @@
                 pinnedMethodSignatures.addAll(getOrComputeLibraryVirtualMethods(superclass)));
       }
       pinnedMethodSignatures.forEach(
-          signature -> reserveMethodSignature(signature, signature, IntSets.EMPTY_SET));
+          signature ->
+              reserveMethodSignature(signature, signature, AllowedPrototypeChanges.empty()));
     }
 
     private void reserveMethodSignature(
         DexMethodSignature newMethodSignature,
         DexMethodSignature originalMethodSignature,
-        IntSet removedParameterIndices) {
+        AllowedPrototypeChanges allowedPrototypeChanges) {
       // Record that methods with the given signature and removed parameters should be mapped to the
       // new signature.
       newMethodSignatures
           .computeIfAbsent(originalMethodSignature, ignoreKey(HashMap::new))
-          .put(removedParameterIndices, newMethodSignature);
+          .put(allowedPrototypeChanges, newMethodSignature);
 
       // Record that the new method signature is used, by a method with the old signature that had
       // the
       // given removed parameters.
       occupiedMethodSignatures.put(
-          newMethodSignature, new Pair<>(removedParameterIndices, originalMethodSignature));
+          newMethodSignature, new Pair<>(allowedPrototypeChanges, originalMethodSignature));
     }
 
-    private void computeRemovableVirtualMethodParameters(
+    private void computePrototypeChangesForVirtualMethods(
         Set<DexProgramClass> stronglyConnectedProgramClasses) {
       // Group the virtual methods in the component by their signatures.
       Map<DexMethodSignature, ProgramMethodSet> virtualMethodsBySignature =
           computeVirtualMethodsBySignature(stronglyConnectedProgramClasses);
       virtualMethodsBySignature.forEach(
           (signature, methods) -> {
-            // Check that there are no keep rules that prohibit parameter removal from any of the
+            // Check that there are no keep rules that prohibit prototype changes from any of the
             // methods.
-            if (Iterables.any(methods, method -> !isParameterRemovalAllowed(method))) {
+            if (Iterables.any(methods, method -> !isPrototypeChangesAllowed(method))) {
               return;
             }
 
@@ -244,10 +298,30 @@
               }
             }
 
-            // If any parameters could be removed, record it.
-            if (!removableVirtualMethodParametersInAllMethods.isEmpty()) {
-              removableVirtualMethodParameters.put(
-                  signature, removableVirtualMethodParametersInAllMethods);
+            // If any prototype changes can be made, record it.
+            SingleValue returnValueForVirtualMethods =
+                getReturnValueForVirtualMethods(signature, methods);
+            boolean canRewriteVirtualMethodsToVoid = returnValueForVirtualMethods != null;
+            if (canRewriteVirtualMethodsToVoid
+                || !removableVirtualMethodParametersInAllMethods.isEmpty()) {
+              allowedPrototypeChangesForVirtualMethods.put(
+                  signature,
+                  new AllowedPrototypeChanges(
+                      canRewriteVirtualMethodsToVoid,
+                      removableVirtualMethodParametersInAllMethods));
+            }
+
+            // Also record the found return value for abstract virtual methods.
+            if (canRewriteVirtualMethodsToVoid) {
+              for (ProgramMethod method : methods) {
+                if (method.getAccessFlags().isAbstract()) {
+                  returnValuesForVirtualMethods.put(method, returnValueForVirtualMethods);
+                } else {
+                  AbstractValue returnValueForVirtualMethod =
+                      method.getOptimizationInfo().getAbstractReturnValue();
+                  assert returnValueForVirtualMethod.equals(returnValueForVirtualMethods);
+                }
+              }
             }
           });
     }
@@ -266,13 +340,47 @@
       return virtualMethodsBySignature;
     }
 
-    private boolean isParameterRemovalAllowed(ProgramMethod method) {
+    private boolean isPrototypeChangesAllowed(ProgramMethod method) {
       return appView.getKeepInfo(method).isParameterRemovalAllowed(options)
           && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
           && !appView.appInfo().isBootstrapMethod(method)
           && !appView.appInfo().isMethodTargetedByInvokeDynamic(method);
     }
 
+    private SingleValue getReturnValueForVirtualMethods(
+        DexMethodSignature signature, ProgramMethodSet methods) {
+      if (signature.getReturnType().isVoidType()) {
+        return null;
+      }
+
+      SingleValue returnValue = null;
+      for (ProgramMethod method : methods) {
+        if (method.getDefinition().isAbstract()) {
+          DexProgramClass holder = method.getHolder();
+          if (holder.isInterface()) {
+            ObjectAllocationInfoCollection objectAllocationInfoCollection =
+                appView.appInfo().getObjectAllocationInfoCollection();
+            if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(holder)) {
+              return null;
+            }
+          }
+          // OK, this can be rewritten to have void return type.
+          continue;
+        }
+        if (!appView.appInfo().mayPropagateValueFor(method)) {
+          return null;
+        }
+        AbstractValue returnValueForMethod = method.getOptimizationInfo().getAbstractReturnValue();
+        if (!returnValueForMethod.isSingleValue()
+            || !returnValueForMethod.asSingleValue().isMaterializableInAllContexts(appView)
+            || (returnValue != null && !returnValueForMethod.equals(returnValue))) {
+          return null;
+        }
+        returnValue = returnValueForMethod.asSingleValue();
+      }
+      return returnValue;
+    }
+
     private boolean canRemoveParameterFromVirtualMethods(
         int parameterIndex, ProgramMethodSet methods) {
       for (ProgramMethod method : methods) {
@@ -316,9 +424,8 @@
           method -> {
             RewrittenPrototypeDescription prototypeChanges =
                 method.getDefinition().belongsToDirectPool()
-                    ? computeRemovableParametersFromDirectMethod(
-                        method, instanceInitializerSignatures)
-                    : computeRemovableParametersFromVirtualMethod(method);
+                    ? computePrototypeChangesForDirectMethod(method, instanceInitializerSignatures)
+                    : computePrototypeChangesForVirtualMethod(method);
             DexMethod newMethodSignature = getNewMethodSignature(method, prototypeChanges);
             if (newMethodSignature != method.getReference()) {
               partialGraphLensBuilder.recordMove(
@@ -332,19 +439,20 @@
     private DexMethod getNewMethodSignature(
         ProgramMethod method, RewrittenPrototypeDescription prototypeChanges) {
       DexMethodSignature methodSignatureWithoutPrototypeChanges = method.getMethodSignature();
-      IntSet removableParameterIndices = prototypeChanges.getArgumentInfoCollection().getKeys();
+      AllowedPrototypeChanges allowedPrototypeChanges =
+          AllowedPrototypeChanges.create(prototypeChanges);
 
       // Check if there is a reserved signature for this already.
       DexMethodSignature reservedSignature =
           newMethodSignatures
               .getOrDefault(methodSignatureWithoutPrototypeChanges, Collections.emptyMap())
-              .get(removableParameterIndices);
+              .get(allowedPrototypeChanges);
       if (reservedSignature != null) {
         return reservedSignature.withHolder(method.getHolderType(), dexItemFactory);
       }
 
       DexMethod methodReferenceWithParametersRemoved =
-          prototypeChanges.getArgumentInfoCollection().rewriteMethod(method, dexItemFactory);
+          prototypeChanges.rewriteMethod(method, dexItemFactory);
       DexMethodSignature methodSignatureWithParametersRemoved =
           methodReferenceWithParametersRemoved.getSignature();
 
@@ -354,50 +462,65 @@
           reserveMethodSignature(
               methodSignatureWithParametersRemoved,
               methodSignatureWithoutPrototypeChanges,
-              removableParameterIndices);
+              allowedPrototypeChanges);
         }
         return methodReferenceWithParametersRemoved;
       }
 
-      Pair<IntSet, DexMethodSignature> occupant =
+      Pair<AllowedPrototypeChanges, DexMethodSignature> occupant =
           occupiedMethodSignatures.get(methodSignatureWithParametersRemoved);
       // In this case we should have found a reserved method signature above.
-      assert !(occupant.getFirst().equals(removableParameterIndices)
+      assert !(occupant.getFirst().equals(allowedPrototypeChanges)
           && occupant.getSecond().equals(methodSignatureWithoutPrototypeChanges));
 
       // We need to find a new name for this method, since the signature is already occupied.
       // TODO(b/190154391): Instead of generating a new name, we could also try permuting the order
       // of parameters.
+      IntBox suffix =
+          newMethodSignatureSuffixes.computeIfAbsent(
+              methodSignatureWithParametersRemoved, ignoreKey(IntBox::new));
       DexMethod newMethod =
           dexItemFactory.createFreshMethodNameWithoutHolder(
               method.getName().toString(),
               methodReferenceWithParametersRemoved.getProto(),
               method.getHolderType(),
               candidate -> {
-                Pair<IntSet, DexMethodSignature> candidateOccupant =
-                    occupiedMethodSignatures.get(candidate.getSignature());
-                if (candidateOccupant == null) {
-                  return true;
-                }
-                return candidateOccupant.getFirst().equals(removableParameterIndices)
-                    && candidateOccupant.getSecond().equals(methodSignatureWithoutPrototypeChanges);
-              });
+                suffix.increment();
+                return isMethodSignatureFresh(
+                    candidate.getSignature(),
+                    methodSignatureWithoutPrototypeChanges,
+                    allowedPrototypeChanges);
+              },
+              suffix.get());
 
       // Reserve the newly generated method signature.
       if (!method.getDefinition().isInstanceInitializer()) {
         reserveMethodSignature(
             newMethod.getSignature(),
             methodSignatureWithoutPrototypeChanges,
-            removableParameterIndices);
+            allowedPrototypeChanges);
       }
 
       return newMethod;
     }
 
-    private RewrittenPrototypeDescription computeRemovableParametersFromDirectMethod(
+    private boolean isMethodSignatureFresh(
+        DexMethodSignature signature,
+        DexMethodSignature previous,
+        AllowedPrototypeChanges allowedPrototypeChanges) {
+      Pair<AllowedPrototypeChanges, DexMethodSignature> candidateOccupant =
+          occupiedMethodSignatures.get(signature);
+      if (candidateOccupant == null) {
+        return true;
+      }
+      return candidateOccupant.getFirst().equals(allowedPrototypeChanges)
+          && candidateOccupant.getSecond().equals(previous);
+    }
+
+    private RewrittenPrototypeDescription computePrototypeChangesForDirectMethod(
         ProgramMethod method, DexMethodSignatureSet instanceInitializerSignatures) {
       assert method.getDefinition().belongsToDirectPool();
-      if (!isParameterRemovalAllowed(method)) {
+      if (!isPrototypeChangesAllowed(method)) {
         return RewrittenPrototypeDescription.none();
       }
       // TODO(b/199864962): Allow parameter removal from check-not-null classified methods.
@@ -422,15 +545,16 @@
       return prototypeChanges;
     }
 
-    private RewrittenPrototypeDescription computeRemovableParametersFromVirtualMethod(
+    private RewrittenPrototypeDescription computePrototypeChangesForVirtualMethod(
         ProgramMethod method) {
-      IntSet removableParameterIndices =
-          removableVirtualMethodParameters.getOrDefault(
-              method.getMethodSignature(), IntSets.EMPTY_SET);
-      if (removableParameterIndices.isEmpty()) {
+      AllowedPrototypeChanges allowedPrototypeChanges =
+          allowedPrototypeChangesForVirtualMethods.get(method.getMethodSignature());
+      if (allowedPrototypeChanges == null) {
         return RewrittenPrototypeDescription.none();
       }
 
+      IntSet removableParameterIndices = allowedPrototypeChanges.removableParameterIndices;
+
       if (method.getAccessFlags().isAbstract()) {
         // Treat the parameters as unused.
         ArgumentInfoCollection.Builder removableParametersBuilder =
@@ -443,26 +567,41 @@
                   .build());
         }
         return RewrittenPrototypeDescription.create(
-            Collections.emptyList(), null, removableParametersBuilder.build());
+            Collections.emptyList(),
+            computeReturnChangesForMethod(method, allowedPrototypeChanges.canRewriteToVoid),
+            removableParametersBuilder.build());
       }
 
       RewrittenPrototypeDescription prototypeChanges =
-          computePrototypeChangesForMethod(method, removableParameterIndices::contains);
+          computePrototypeChangesForMethod(
+              method,
+              allowedPrototypeChanges.canRewriteToVoid,
+              removableParameterIndices::contains);
       assert prototypeChanges.getArgumentInfoCollection().size()
           == removableParameterIndices.size();
       return prototypeChanges;
     }
 
     private RewrittenPrototypeDescription computePrototypeChangesForMethod(ProgramMethod method) {
-      return computePrototypeChangesForMethod(method, parameterIndex -> true);
+      return computePrototypeChangesForMethod(method, true, parameterIndex -> true);
     }
 
     private RewrittenPrototypeDescription computePrototypeChangesForMethod(
+        ProgramMethod method,
+        boolean allowToVoidRewriting,
+        IntPredicate removableParameterIndices) {
+      return RewrittenPrototypeDescription.create(
+          Collections.emptyList(),
+          computeReturnChangesForMethod(method, allowToVoidRewriting),
+          computeParameterChangesForMethod(method, removableParameterIndices));
+    }
+
+    private ArgumentInfoCollection computeParameterChangesForMethod(
         ProgramMethod method, IntPredicate removableParameterIndices) {
       ConcreteCallSiteOptimizationInfo optimizationInfo =
           method.getDefinition().getCallSiteOptimizationInfo().asConcreteCallSiteOptimizationInfo();
       if (optimizationInfo == null) {
-        return RewrittenPrototypeDescription.none();
+        return ArgumentInfoCollection.empty();
       }
 
       ArgumentInfoCollection.Builder removableParametersBuilder = ArgumentInfoCollection.builder();
@@ -483,8 +622,34 @@
                   .build());
         }
       }
-      return RewrittenPrototypeDescription.create(
-          Collections.emptyList(), null, removableParametersBuilder.build());
+      return removableParametersBuilder.build();
+    }
+
+    private RewrittenTypeInfo computeReturnChangesForMethod(
+        ProgramMethod method, boolean allowToVoidRewriting) {
+      if (!allowToVoidRewriting) {
+        assert !returnValuesForVirtualMethods.containsKey(method);
+        return null;
+      }
+
+      AbstractValue returnValue;
+      if (method.getReturnType().isAlwaysNull(appView)) {
+        returnValue = appView.abstractValueFactory().createNullValue();
+      } else if (method.getDefinition().belongsToVirtualPool()
+          && returnValuesForVirtualMethods.containsKey(method)) {
+        assert method.getAccessFlags().isAbstract();
+        returnValue = returnValuesForVirtualMethods.get(method);
+      } else {
+        returnValue = method.getOptimizationInfo().getAbstractReturnValue();
+      }
+
+      if (!returnValue.isSingleValue()
+          || !returnValue.asSingleValue().isMaterializableInAllContexts(appView)) {
+        return null;
+      }
+
+      SingleValue singleValue = returnValue.asSingleValue();
+      return RewrittenTypeInfo.toVoid(method.getReturnType(), dexItemFactory, singleValue);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 11dcc13..c474a8a 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker.VerifyMappingFileHashResult;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
 import com.android.tools.r8.retrace.internal.RetracerImpl;
@@ -18,25 +20,37 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Charsets;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+import com.google.common.io.CharStreams;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
+import java.io.Reader;
 import java.io.UnsupportedEncodingException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Scanner;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -51,7 +65,7 @@
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
           "Usage: retrace <proguard-map> [stack-trace-file] "
-              + "[--regex <regexp>, --verbose, --info, --quiet]",
+              + "[--regex <regexp>, --verbose, --info, --quiet, --verify-mapping-file-hash]",
           "  where <proguard-map> is an r8 generated mapping file.");
 
   private static Builder parseArguments(String[] args, DiagnosticsHandler diagnosticsHandler) {
@@ -89,6 +103,12 @@
         builder.setRegularExpression(regex);
         continue;
       }
+      Boolean verify = OptionsParsing.tryParseBoolean(context, "--verify-mapping-file-hash");
+      if (verify != null) {
+        builder.setVerifyMappingFileHash(true);
+        hasSetStackTrace = true;
+        continue;
+      }
       if (!hasSetProguardMap) {
         builder.setProguardMapProducer(getMappingSupplier(context.head(), diagnosticsHandler));
         context.next();
@@ -158,7 +178,7 @@
    * @param stackTrace the stack trace to be retrace
    * @return list of potentially ambiguous stack traces.
    */
-  public List<List<T>> retraceStackTrace(List<T> stackTrace) {
+  public List<Iterator<T>> retraceStackTrace(List<T> stackTrace) {
     ListUtils.forEachWithIndex(
         stackTrace,
         (line, lineNumber) -> {
@@ -168,57 +188,38 @@
             throw new RetraceAbortException();
           }
         });
-    List<Pair<List<T>, RetraceStackTraceContext>> retracedStackTraces = new ArrayList<>();
-    retracedStackTraces.add(
-        new Pair<>(new ArrayList<>(), RetraceStackTraceContext.getInitialContext()));
-    retracedStackTraces =
+    RetraceStackTraceElementProxyEquivalence<T, ST> equivalence =
+        new RetraceStackTraceElementProxyEquivalence<>(isVerbose);
+    RetracedNodeState<T, ST> root = RetracedNodeState.initial(equivalence);
+    List<RetracedNodeState<T, ST>> allLeaves =
         ListUtils.fold(
             stackTrace,
-            retracedStackTraces,
+            (List<RetracedNodeState<T, ST>>) ImmutableList.of(root),
             (acc, stackTraceLine) -> {
               ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
-              List<Pair<List<T>, RetraceStackTraceContext>> newRetracedStackTraces =
-                  new ArrayList<>();
-              for (Pair<List<T>, RetraceStackTraceContext> retracedStackTrace : acc) {
-                Map<
-                        RetraceStackTraceElementProxy<T, ST>,
-                        List<RetraceStackTraceElementProxy<T, ST>>>
-                    ambiguousBlocks = new HashMap<>();
-                List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
+              List<RetracedNodeState<T, ST>> newLeaves = new ArrayList<>();
+              for (RetracedNodeState<T, ST> previousNode : acc) {
                 proxyRetracer
-                    .retrace(parsedLine, retracedStackTrace.getSecond())
+                    .retrace(parsedLine, previousNode.context)
                     .forEach(
                         retracedElement -> {
                           if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
-                            ambiguousKeys.add(retracedElement);
-                            ambiguousBlocks.put(retracedElement, new ArrayList<>());
+                            previousNode.addChild(retracedElement, retracedElement.getContext());
                           }
-                          ambiguousBlocks.get(ListUtils.last(ambiguousKeys)).add(retracedElement);
+                          previousNode.addFrameToCurrentChild(
+                              parsedLine.toRetracedItem(retracedElement, isVerbose));
                         });
-                if (ambiguousKeys.isEmpty()) {
-                  // This happens when there is nothing to report.
-                  newRetracedStackTraces.add(
-                      new Pair<>(
-                          retracedStackTrace.getFirst(),
-                          RetraceStackTraceContext.getInitialContext()));
-                  continue;
+                if (!previousNode.hasChildren()) {
+                  // This happens when there is nothing to retrace. Add the node to newLeaves to
+                  // ensure we keep retracing this path.
+                  previousNode.addChild(null, RetraceStackTraceContext.empty());
                 }
-                Collections.sort(ambiguousKeys);
-                ambiguousKeys.forEach(
-                    key -> {
-                      List<T> resultList = new ArrayList<>();
-                      resultList.addAll(retracedStackTrace.getFirst());
-                      resultList.addAll(
-                          ListUtils.map(
-                              ambiguousBlocks.get(key),
-                              retracedElement ->
-                                  parsedLine.toRetracedItem(retracedElement, isVerbose)));
-                      newRetracedStackTraces.add(new Pair<>(resultList, key.getContext()));
-                    });
+                newLeaves.addAll(previousNode.getChildren());
               }
-              return newRetracedStackTraces;
+              return newLeaves;
             });
-    return ListUtils.map(retracedStackTraces, Pair::getFirst);
+    assert !allLeaves.isEmpty();
+    return ListUtils.map(allLeaves, RetracedNodeState::iterator);
   }
 
   /**
@@ -232,7 +233,7 @@
     List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
     ST parsedLine = stackTraceLineParser.parse(stackTraceFrame);
     proxyRetracer
-        .retrace(parsedLine, RetraceStackTraceContext.getInitialContext())
+        .retrace(parsedLine, RetraceStackTraceContext.empty())
         .forEach(
             retracedElement -> {
               if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
@@ -259,7 +260,7 @@
   public List<T> retraceLine(T stackTraceLine) {
     ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
     return proxyRetracer
-        .retrace(parsedLine, RetraceStackTraceContext.getInitialContext())
+        .retrace(parsedLine, RetraceStackTraceContext.empty())
         .map(
             retraceFrame -> {
               retraceFrame.getOriginalItem().toRetracedItem(retraceFrame, isVerbose);
@@ -282,8 +283,31 @@
   static void runForTesting(RetraceCommand command, boolean allowExperimentalMapping) {
     try {
       Timing timing = Timing.create("R8 retrace", command.printMemory());
-      timing.begin("Read proguard map");
       RetraceOptions options = command.getOptions();
+      if (command.getOptions().isVerifyMappingFileHash()) {
+        try (Reader reader = options.getProguardMapProducer().get()) {
+          VerifyMappingFileHashResult checkResult =
+              ProguardMapChecker.validateProguardMapHash(CharStreams.toString(reader));
+          if (checkResult.isError()) {
+            command
+                .getOptions()
+                .getDiagnosticsHandler()
+                .error(new StringDiagnostic(checkResult.getMessage()));
+            throw new RuntimeException(checkResult.getMessage());
+          }
+          if (!checkResult.isOk()) {
+            command
+                .getOptions()
+                .getDiagnosticsHandler()
+                .warning(new StringDiagnostic(checkResult.getMessage()));
+          }
+        } catch (IOException e) {
+          command.getOptions().getDiagnosticsHandler().error(new ExceptionDiagnostic(e));
+          throw new RuntimeException(e);
+        }
+        return;
+      }
+      timing.begin("Read proguard map");
       DiagnosticsHandler diagnosticsHandler = options.getDiagnosticsHandler();
       // The setup of a retracer should likely also follow a builder pattern instead of having
       // static create methods. That would avoid the need to method overload the construction here
@@ -433,4 +457,200 @@
       }
     }
   }
+
+  private static class RetraceStackTraceElementProxyEquivalence<
+          T, ST extends StackTraceElementProxy<T, ST>>
+      extends Equivalence<RetraceStackTraceElementProxy<T, ST>> {
+
+    private final boolean isVerbose;
+
+    public RetraceStackTraceElementProxyEquivalence(boolean isVerbose) {
+      this.isVerbose = isVerbose;
+    }
+
+    @Override
+    protected boolean doEquivalent(
+        RetraceStackTraceElementProxy<T, ST> one, RetraceStackTraceElementProxy<T, ST> other) {
+      if (one == other) {
+        return true;
+      }
+      if (testNotEqualProperty(
+              one,
+              other,
+              RetraceStackTraceElementProxy::hasRetracedClass,
+              r -> r.getRetracedClass().getTypeName())
+          || testNotEqualProperty(
+              one,
+              other,
+              RetraceStackTraceElementProxy::hasSourceFile,
+              RetraceStackTraceElementProxy::getSourceFile)
+          // TODO(b/201042571): This will have to change.
+          || testNotEqualProperty(
+              one,
+              other,
+              RetraceStackTraceElementProxy::hasLineNumber,
+              RetraceStackTraceElementProxy::getLineNumber)) {
+        return false;
+      }
+      if (one.hasRetracedMethod() != other.hasRetracedMethod()) {
+        return false;
+      }
+      if (one.hasRetracedMethod()) {
+        RetracedMethodReference oneMethod = one.getRetracedMethod();
+        RetracedMethodReference otherMethod = other.getRetracedMethod();
+        if (oneMethod.isKnown() != otherMethod.isKnown()) {
+          return false;
+        }
+        // In verbose mode we check the signature, otherwise we only check the name
+        if (!oneMethod.getMethodName().equals(otherMethod.getMethodName())) {
+          return false;
+        }
+        if (isVerbose
+            && ((oneMethod.isKnown()
+                    && !oneMethod
+                        .asKnown()
+                        .getMethodReference()
+                        .toString()
+                        .equals(otherMethod.asKnown().getMethodReference().toString()))
+                || (!oneMethod.isKnown()
+                    && !oneMethod.getMethodName().equals(otherMethod.getMethodName())))) {
+          return false;
+        }
+      }
+      if (one.hasRetracedField() != other.hasRetracedField()) {
+        return false;
+      }
+      if (one.hasRetracedField()) {
+        RetracedFieldReference oneField = one.getRetracedField();
+        RetracedFieldReference otherField = other.getRetracedField();
+        if (oneField.isKnown() != otherField.isKnown()) {
+          return false;
+        }
+        if (!oneField.getFieldName().equals(otherField.getFieldName())) {
+          return false;
+        }
+        if (isVerbose
+            && ((oneField.isKnown()
+                    && !oneField
+                        .asKnown()
+                        .getFieldReference()
+                        .toString()
+                        .equals(otherField.asKnown().getFieldReference().toString()))
+                || (oneField.isUnknown()
+                    && !oneField.getFieldName().equals(otherField.getFieldName())))) {
+          return false;
+        }
+      }
+      if (one.hasRetracedFieldOrReturnType() != other.hasRetracedFieldOrReturnType()) {
+        return false;
+      }
+      if (one.hasRetracedFieldOrReturnType()) {
+        RetracedTypeReference oneFieldOrReturn = one.getRetracedFieldOrReturnType();
+        RetracedTypeReference otherFieldOrReturn = other.getRetracedFieldOrReturnType();
+        if (!compareRetracedTypeReference(oneFieldOrReturn, otherFieldOrReturn)) {
+          return false;
+        }
+      }
+      if (one.hasRetracedMethodArguments() != other.hasRetracedMethodArguments()) {
+        return false;
+      }
+      if (one.hasRetracedMethodArguments()) {
+        List<RetracedTypeReference> oneMethodArguments = one.getRetracedMethodArguments();
+        List<RetracedTypeReference> otherMethodArguments = other.getRetracedMethodArguments();
+        if (oneMethodArguments.size() != otherMethodArguments.size()) {
+          return false;
+        }
+        for (int i = 0; i < oneMethodArguments.size(); i++) {
+          if (compareRetracedTypeReference(
+              oneMethodArguments.get(i), otherMethodArguments.get(i))) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    private boolean compareRetracedTypeReference(
+        RetracedTypeReference one, RetracedTypeReference other) {
+      return one.isVoid() == other.isVoid()
+          && (one.isVoid() || one.getTypeName().equals(other.getTypeName()));
+    }
+
+    @Override
+    protected int doHash(RetraceStackTraceElementProxy<T, ST> proxy) {
+      return 0;
+    }
+
+    private <V extends Comparable<V>> boolean testNotEqualProperty(
+        RetraceStackTraceElementProxy<T, ST> one,
+        RetraceStackTraceElementProxy<T, ST> other,
+        Function<RetraceStackTraceElementProxy<T, ST>, Boolean> predicate,
+        Function<RetraceStackTraceElementProxy<T, ST>, V> getter) {
+      return Comparator.comparing(predicate).thenComparing(getter).compare(one, other) != 0;
+    }
+
+    public static <T, ST extends StackTraceElementProxy<T, ST>>
+        RetraceStackTraceElementProxyEquivalence<T, ST> getInstance(boolean isVerbose) {
+      return new RetraceStackTraceElementProxyEquivalence<>(isVerbose);
+    }
+  }
+
+  private static class RetracedNodeState<T, ST extends StackTraceElementProxy<T, ST>>
+      implements Iterable<T> {
+
+    private final RetracedNodeState<T, ST> parent;
+    private final Set<Wrapper<RetraceStackTraceElementProxy<T, ST>>> seenSet = new HashSet<>();
+    private final Map<RetraceStackTraceElementProxy<T, ST>, RetracedNodeState<T, ST>> children =
+        new TreeMap<>(Comparator.nullsFirst(Comparator.naturalOrder()));
+    private RetracedNodeState<T, ST> currentChild;
+    private final RetraceStackTraceContext context;
+    private final List<T> frames = new ArrayList<>();
+    private final RetraceStackTraceElementProxyEquivalence<T, ST> equivalence;
+
+    private RetracedNodeState(
+        RetracedNodeState<T, ST> parent,
+        RetraceStackTraceContext context,
+        RetraceStackTraceElementProxyEquivalence<T, ST> equivalence) {
+      this.parent = parent;
+      this.context = context;
+      this.equivalence = equivalence;
+    }
+
+    private void addFrameToCurrentChild(T frame) {
+      if (currentChild != null) {
+        currentChild.frames.add(frame);
+      }
+    }
+
+    private void addChild(
+        RetraceStackTraceElementProxy<T, ST> element, RetraceStackTraceContext context) {
+      if (seenSet.add(equivalence.wrap(element))) {
+        RetracedNodeState<T, ST> newChild = new RetracedNodeState<>(this, context, equivalence);
+        this.currentChild = newChild;
+        children.put(element, newChild);
+      } else {
+        this.currentChild = null;
+      }
+    }
+
+    private static <T, ST extends StackTraceElementProxy<T, ST>> RetracedNodeState<T, ST> initial(
+        RetraceStackTraceElementProxyEquivalence<T, ST> equivalence) {
+      return new RetracedNodeState<>(null, RetraceStackTraceContext.empty(), equivalence);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+      return parent != null
+          ? Iterators.concat(parent.iterator(), frames.iterator())
+          : frames.iterator();
+    }
+
+    public boolean hasChildren() {
+      return !children.isEmpty();
+    }
+
+    public Collection<? extends RetracedNodeState<T, ST>> getChildren() {
+      return children.values();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java
index 6e61118..f7830c3 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassElement.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
 import java.util.List;
-import java.util.Optional;
+import java.util.OptionalInt;
 
 @Keep
 public interface RetraceClassElement extends RetraceElement<RetraceClassResult> {
@@ -20,17 +20,18 @@
 
   RetraceMethodResult lookupMethod(String methodName);
 
-  RetraceFrameResult lookupFrame(Optional<Integer> position, String methodName);
+  RetraceFrameResult lookupFrame(
+      RetraceStackTraceContext context, OptionalInt position, String methodName);
 
   RetraceFrameResult lookupFrame(
-      Optional<Integer> position,
+      RetraceStackTraceContext context,
+      OptionalInt position,
       String methodName,
       List<TypeReference> formalTypes,
       TypeReference returnType);
 
-  RetraceFrameResult lookupFrame(Optional<Integer> position, MethodReference methodReference);
+  RetraceFrameResult lookupFrame(
+      RetraceStackTraceContext context, OptionalInt position, MethodReference methodReference);
 
   RetraceUnknownJsonMappingInformationResult getUnknownJsonMappingInformation();
-
-  RetraceStackTraceContext getContextWhereClassWasThrown();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index e2b59fa..9f5bc71 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.references.TypeReference;
 import java.util.List;
-import java.util.Optional;
+import java.util.OptionalInt;
 
 @Keep
 public interface RetraceClassResult extends RetraceResult<RetraceClassElement> {
@@ -24,12 +24,14 @@
       String methodName, List<TypeReference> formalTypes, TypeReference returnType);
 
   RetraceFrameResult lookupFrame(
-      RetraceStackTraceContext context, Optional<Integer> position, String methodName);
+      RetraceStackTraceContext context, OptionalInt position, String methodName);
 
   RetraceFrameResult lookupFrame(
       RetraceStackTraceContext context,
-      Optional<Integer> position,
+      OptionalInt position,
       String methodName,
       List<TypeReference> formalTypes,
       TypeReference returnType);
+
+  RetraceThrownExceptionResult lookupThrownException(RetraceStackTraceContext context);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index 76d6b77..c627248 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -24,7 +24,8 @@
       ProguardMapProducer proguardMapProducer,
       List<String> stackTrace,
       Consumer<List<String>> retracedStackTraceConsumer,
-      boolean isVerbose) {
+      boolean isVerbose,
+      boolean verifyMappingFileHash) {
     this.stackTrace = stackTrace;
     this.retracedStackTraceConsumer = retracedStackTraceConsumer;
     this.options =
@@ -32,9 +33,10 @@
             .setRegularExpression(regularExpression)
             .setProguardMapProducer(proguardMapProducer)
             .setVerbose(isVerbose)
+            .setVerifyMappingFileHash(verifyMappingFileHash)
             .build();
 
-    assert this.stackTrace != null;
+    assert this.stackTrace != null || verifyMappingFileHash;
     assert this.retracedStackTraceConsumer != null;
   }
 
@@ -81,6 +83,7 @@
     private String regularExpression = StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
     private List<String> stackTrace;
     private Consumer<List<String>> retracedStackTraceConsumer;
+    private boolean verifyMappingFileHash = false;
 
     private Builder(DiagnosticsHandler diagnosticsHandler) {
       this.diagnosticsHandler = diagnosticsHandler;
@@ -125,6 +128,12 @@
       return this;
     }
 
+    /** Set if the mapping-file hash should be checked if present. */
+    public Builder setVerifyMappingFileHash(boolean verifyMappingFileHash) {
+      this.verifyMappingFileHash = verifyMappingFileHash;
+      return this;
+    }
+
     /**
      * Set a consumer for receiving the retraced stack trace.
      *
@@ -142,7 +151,7 @@
       if (this.proguardMapProducer == null) {
         throw new RuntimeException("ProguardMapSupplier not specified");
       }
-      if (this.stackTrace == null) {
+      if (this.stackTrace == null && !verifyMappingFileHash) {
         throw new RuntimeException("StackTrace not specified");
       }
       if (this.retracedStackTraceConsumer == null) {
@@ -154,7 +163,8 @@
           proguardMapProducer,
           stackTrace,
           retracedStackTraceConsumer,
-          isVerbose);
+          isVerbose,
+          verifyMappingFileHash);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
index f2ad4e7..419f6b3 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
@@ -5,7 +5,8 @@
 
 import com.android.tools.r8.Keep;
 import java.util.List;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 @Keep
 public interface RetraceFrameElement extends RetraceElement<RetraceFrameResult> {
@@ -16,10 +17,13 @@
 
   RetraceClassElement getClassElement();
 
-  void visitAllFrames(BiConsumer<RetracedMethodReference, Integer> consumer);
+  void forEach(Consumer<RetracedSingleFrame> consumer);
 
-  void visitRewrittenFrames(
-      RetraceStackTraceContext context, BiConsumer<RetracedMethodReference, Integer> consumer);
+  Stream<RetracedSingleFrame> stream();
+
+  void forEachRewritten(RetraceStackTraceContext context, Consumer<RetracedSingleFrame> consumer);
+
+  Stream<RetracedSingleFrame> streamRewritten(RetraceStackTraceContext context);
 
   RetracedSourceFile getSourceFile(RetracedClassMemberReference frame);
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 9abb027..805443c 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -9,6 +9,5 @@
 @Keep
 public interface RetraceMethodResult extends RetraceResult<RetraceMethodElement> {
 
-  RetraceFrameResult narrowByPosition(int position);
-
+  RetraceFrameResult narrowByPosition(RetraceStackTraceContext context, int position);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
index 47d8c74..2543f35 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
@@ -17,6 +17,7 @@
 public class RetraceOptions {
 
   private final boolean isVerbose;
+  private final boolean verifyMappingFileHash;
   private final String regularExpression;
   private final DiagnosticsHandler diagnosticsHandler;
   private final ProguardMapProducer proguardMapProducer;
@@ -25,11 +26,13 @@
       String regularExpression,
       DiagnosticsHandler diagnosticsHandler,
       ProguardMapProducer proguardMapProducer,
-      boolean isVerbose) {
+      boolean isVerbose,
+      boolean verifyMappingFileHash) {
     this.regularExpression = regularExpression;
     this.diagnosticsHandler = diagnosticsHandler;
     this.proguardMapProducer = proguardMapProducer;
     this.isVerbose = isVerbose;
+    this.verifyMappingFileHash = verifyMappingFileHash;
 
     assert diagnosticsHandler != null;
     assert proguardMapProducer != null;
@@ -39,6 +42,10 @@
     return isVerbose;
   }
 
+  public boolean isVerifyMappingFileHash() {
+    return verifyMappingFileHash;
+  }
+
   public String getRegularExpression() {
     return regularExpression;
   }
@@ -69,6 +76,7 @@
   public static class Builder {
 
     private boolean isVerbose;
+    private boolean verifyMappingFileHash;
     private final DiagnosticsHandler diagnosticsHandler;
     private ProguardMapProducer proguardMapProducer;
     private String regularExpression = defaultRegularExpression();
@@ -83,6 +91,12 @@
       return this;
     }
 
+    /** Set if the mapping-file hash should be checked if present. */
+    public Builder setVerifyMappingFileHash(boolean verifyMappingFileHash) {
+      this.verifyMappingFileHash = verifyMappingFileHash;
+      return this;
+    }
+
     /**
      * Set a producer for the proguard mapping contents.
      *
@@ -116,7 +130,11 @@
         throw new RuntimeException("Regular expression not specified");
       }
       return new RetraceOptions(
-          regularExpression, diagnosticsHandler, proguardMapProducer, isVerbose);
+          regularExpression,
+          diagnosticsHandler,
+          proguardMapProducer,
+          isVerbose,
+          verifyMappingFileHash);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceContext.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceContext.java
index e87ff95..5af984a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceContext.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceContext.java
@@ -10,7 +10,7 @@
 @Keep
 public interface RetraceStackTraceContext {
 
-  static RetraceStackTraceContext getInitialContext() {
+  static RetraceStackTraceContext empty() {
     return RetraceStackTraceContextImpl.builder().build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
index c148bc0..7f4747a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
@@ -25,9 +25,9 @@
 
   boolean hasLineNumber();
 
-  boolean hasFieldOrReturnType();
+  boolean hasRetracedFieldOrReturnType();
 
-  boolean hasMethodArguments();
+  boolean hasRetracedMethodArguments();
 
   ST getOriginalItem();
 
@@ -39,7 +39,7 @@
 
   RetracedTypeReference getRetracedFieldOrReturnType();
 
-  List<RetracedTypeReference> getMethodArguments();
+  List<RetracedTypeReference> getRetracedMethodArguments();
 
   String getSourceFile();
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceThrownExceptionElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceThrownExceptionElement.java
new file mode 100644
index 0000000..1af093c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceThrownExceptionElement.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceThrownExceptionElement
+    extends RetraceElement<RetraceThrownExceptionResult> {
+
+  RetracedSourceFile getSourceFile();
+
+  RetracedClassReference getRetracedClass();
+
+  RetraceStackTraceContext getContext();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceThrownExceptionResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceThrownExceptionResult.java
new file mode 100644
index 0000000..b53ec9b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceThrownExceptionResult.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetraceThrownExceptionResult
+    extends RetraceResult<RetraceThrownExceptionElement> {}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedSingleFrame.java b/src/main/java/com/android/tools/r8/retrace/RetracedSingleFrame.java
new file mode 100644
index 0000000..6f14809
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedSingleFrame.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetracedSingleFrame {
+
+  RetracedMethodReference getMethodReference();
+
+  int getIndex();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index bd865a8..34d413b 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -29,6 +29,9 @@
 
   RetraceTypeResult retraceType(TypeReference typeReference);
 
+  RetraceThrownExceptionResult retraceThrownException(
+      ClassReference exception, RetraceStackTraceContext context);
+
   static Retracer createDefault(
       ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
     return RetracerImpl.create(proguardMapProducer, diagnosticsHandler, false);
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index ff5b970..09a6931 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -9,10 +9,12 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -77,48 +79,37 @@
    */
   public List<String> retrace(List<String> stackTrace) {
     List<String> retracedStrings = new ArrayList<>();
-    List<List<String>> retracedStackTraces =
-        removeDuplicateStackTraces(retraceStackTrace(stackTrace));
+    List<Iterator<String>> retracedStackTraces = retraceStackTrace(stackTrace);
     if (retracedStackTraces.size() > 1 && isVerbose) {
       retracedStrings.add("There are " + retracedStackTraces.size() + " ambiguous stack traces.");
     }
     for (int i = 0; i < retracedStackTraces.size(); i++) {
-      List<String> result = retracedStackTraces.get(i);
-      if (i > 0 && !result.isEmpty()) {
-        // We are reporting an ambiguous frame. To support retracing tools that retrace line by line
-        // we have to emit <OR> at the point of the first ' at ' if we can find it.
-        String firstLine = result.get(0);
-        int indexToInsertOr = firstLine.indexOf(" at ");
-        boolean hasSpace = indexToInsertOr >= 0;
-        if (indexToInsertOr < 0) {
-          indexToInsertOr = Math.max(StringUtils.firstNonWhitespaceCharacter(firstLine), 0);
-        }
-        result.set(
-            0,
-            firstLine.substring(0, indexToInsertOr)
-                + (hasSpace ? "<OR>" : "<OR> ")
-                + firstLine.substring(indexToInsertOr));
-      }
-      retracedStrings.addAll(result);
+      Iterator<String> result = retracedStackTraces.get(i);
+      BooleanBox insertOr = new BooleanBox(i > 0);
+      result.forEachRemaining(
+          stackTraceLine -> {
+            if (insertOr.get()) {
+              // We are reporting an ambiguous frame. To support retracing tools that retrace line
+              // by line we have to emit <OR> at the point of the first ' at ' if we can find it.
+              int indexToInsertOr = stackTraceLine.indexOf(" at ");
+              boolean hasSpace = indexToInsertOr >= 0;
+              if (indexToInsertOr < 0) {
+                indexToInsertOr =
+                    Math.max(StringUtils.firstNonWhitespaceCharacter(stackTraceLine), 0);
+              }
+              retracedStrings.add(
+                  stackTraceLine.substring(0, indexToInsertOr)
+                      + (hasSpace ? "<OR>" : "<OR> ")
+                      + stackTraceLine.substring(indexToInsertOr));
+              insertOr.set(false);
+            } else {
+              retracedStrings.add(stackTraceLine);
+            }
+          });
     }
     return retracedStrings;
   }
 
-  private List<List<String>> removeDuplicateStackTraces(List<List<String>> stackTraces) {
-    if (stackTraces.size() == 1) {
-      return stackTraces;
-    }
-    Set<List<String>> seenStackTraces = new HashSet<>();
-    List<List<String>> nonDuplicateStackTraces = new ArrayList<>();
-    stackTraces.forEach(
-        stackTrace -> {
-          if (seenStackTraces.add(stackTrace)) {
-            nonDuplicateStackTraces.add(stackTrace);
-          }
-        });
-    return nonDuplicateStackTraces;
-  }
-
   /**
    * Retraces a single stack trace line and returns the potential list of original frames
    *
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index 0f165cf..f94035b 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -25,7 +25,7 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
+import java.util.OptionalInt;
 import java.util.function.BiFunction;
 import java.util.stream.Stream;
 
@@ -47,10 +47,6 @@
     return new RetraceClassResultImpl(obfuscatedReference, mapper, retracer);
   }
 
-  RetracerImpl getRetracerImpl() {
-    return retracer;
-  }
-
   @Override
   public RetraceFieldResultImpl lookupField(String fieldName) {
     return lookupField(FieldDefinition.create(obfuscatedReference, fieldName));
@@ -124,25 +120,32 @@
 
   @Override
   public RetraceFrameResultImpl lookupFrame(
-      RetraceStackTraceContext context, Optional<Integer> position, String methodName) {
-    return lookupFrame(MethodDefinition.create(obfuscatedReference, methodName), position);
+      RetraceStackTraceContext context, OptionalInt position, String methodName) {
+    return lookupFrame(context, position, MethodDefinition.create(obfuscatedReference, methodName));
   }
 
   @Override
   public RetraceFrameResultImpl lookupFrame(
       RetraceStackTraceContext context,
-      Optional<Integer> position,
+      OptionalInt position,
       String methodName,
       List<TypeReference> formalTypes,
       TypeReference returnType) {
     return lookupFrame(
+        context,
+        position,
         MethodDefinition.create(
-            Reference.method(obfuscatedReference, methodName, formalTypes, returnType)),
-        position);
+            Reference.method(obfuscatedReference, methodName, formalTypes, returnType)));
+  }
+
+  @Override
+  public RetraceThrownExceptionResultImpl lookupThrownException(RetraceStackTraceContext context) {
+    return new RetraceThrownExceptionResultImpl(
+        (RetraceStackTraceContextImpl) context, obfuscatedReference, mapper);
   }
 
   private RetraceFrameResultImpl lookupFrame(
-      MethodDefinition definition, Optional<Integer> position) {
+      RetraceStackTraceContext context, OptionalInt position, MethodDefinition definition) {
     List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappings = new ArrayList<>();
     internalStream()
         .forEach(
@@ -153,11 +156,12 @@
                         mappings.add(new Pair<>(element, mappedRanges));
                       });
             });
-    return new RetraceFrameResultImpl(this, mappings, definition, position, retracer);
+    return new RetraceFrameResultImpl(
+        this, mappings, definition, position, retracer, (RetraceStackTraceContextImpl) context);
   }
 
   private List<List<MappedRange>> getMappedRangesForFrame(
-      RetraceClassElementImpl element, MethodDefinition definition, Optional<Integer> position) {
+      RetraceClassElementImpl element, MethodDefinition definition, OptionalInt position) {
     List<List<MappedRange>> overloadedRanges = new ArrayList<>();
     if (mapper == null) {
       overloadedRanges.add(null);
@@ -170,8 +174,8 @@
       return overloadedRanges;
     }
     List<MappedRange> mappedRangesForPosition = null;
-    if (position.isPresent() && position.get() >= 0) {
-      mappedRangesForPosition = mappedRanges.allRangesForLine(position.get(), false);
+    if (position.isPresent() && position.getAsInt() >= 0) {
+      mappedRangesForPosition = mappedRanges.allRangesForLine(position.getAsInt(), false);
     }
     if (mappedRangesForPosition == null || mappedRangesForPosition.isEmpty()) {
       mappedRangesForPosition = mappedRanges.getMappedRanges();
@@ -228,7 +232,7 @@
     private final RetracedClassReferenceImpl classReference;
     private final ClassNamingForNameMapper mapper;
 
-    public RetraceClassElementImpl(
+    private RetraceClassElementImpl(
         RetraceClassResultImpl classResult,
         RetracedClassReferenceImpl classReference,
         ClassNamingForNameMapper mapper) {
@@ -244,8 +248,8 @@
 
     @Override
     public RetracedSourceFile getSourceFile() {
-      if (classResult.mapper != null) {
-        for (MappingInformation info : classResult.mapper.getAdditionalMappingInfo()) {
+      if (mapper != null) {
+        for (MappingInformation info : mapper.getAdditionalMappingInfo()) {
           if (info.isFileNameInformation()) {
             return new RetracedSourceFileImpl(info.asFileNameInformation().getFileName());
           }
@@ -272,13 +276,6 @@
     }
 
     @Override
-    public RetraceStackTraceContext getContextWhereClassWasThrown() {
-      return RetraceStackTraceContextImpl.builder()
-          .setSeenException(getRetracedClass().getClassReference())
-          .build();
-    }
-
-    @Override
     public RetraceFieldResultImpl lookupField(String fieldName) {
       return lookupField(FieldDefinition.create(classReference.getClassReference(), fieldName));
     }
@@ -332,18 +329,23 @@
     }
 
     @Override
-    public RetraceFrameResultImpl lookupFrame(Optional<Integer> position, String methodName) {
+    public RetraceFrameResultImpl lookupFrame(
+        RetraceStackTraceContext context, OptionalInt position, String methodName) {
       return lookupFrame(
-          position, MethodDefinition.create(classReference.getClassReference(), methodName));
+          context,
+          position,
+          MethodDefinition.create(classReference.getClassReference(), methodName));
     }
 
     @Override
     public RetraceFrameResult lookupFrame(
-        Optional<Integer> position,
+        RetraceStackTraceContext context,
+        OptionalInt position,
         String methodName,
         List<TypeReference> formalTypes,
         TypeReference returnType) {
       return lookupFrame(
+          context,
           position,
           MethodDefinition.create(
               Reference.method(
@@ -352,8 +354,8 @@
 
     @Override
     public RetraceFrameResult lookupFrame(
-        Optional<Integer> position, MethodReference methodReference) {
-      return lookupFrame(position, MethodDefinition.create(methodReference));
+        RetraceStackTraceContext context, OptionalInt position, MethodReference methodReference) {
+      return lookupFrame(context, position, MethodDefinition.create(methodReference));
     }
 
     @Override
@@ -363,7 +365,7 @@
     }
 
     private RetraceFrameResultImpl lookupFrame(
-        Optional<Integer> position, MethodDefinition definition) {
+        RetraceStackTraceContext context, OptionalInt position, MethodDefinition definition) {
       MethodDefinition methodDefinition =
           MethodDefinition.create(classReference.getClassReference(), definition.getName());
       ImmutableList.Builder<Pair<RetraceClassElementImpl, List<MappedRange>>> builder =
@@ -375,7 +377,12 @@
                 builder.add(new Pair<>(this, mappedRanges));
               });
       return new RetraceFrameResultImpl(
-          classResult, builder.build(), methodDefinition, position, classResult.retracer);
+          classResult,
+          builder.build(),
+          methodDefinition,
+          position,
+          classResult.retracer,
+          (RetraceStackTraceContextImpl) context);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 9bee685..a2936bf 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.retrace.RetraceInvalidRewriteFrameDiagnostics;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedClassMemberReference;
-import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
 import com.android.tools.r8.retrace.RetracedSourceFile;
 import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
 import com.android.tools.r8.utils.ListUtils;
@@ -26,17 +26,18 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
-import java.util.function.BiConsumer;
+import java.util.OptionalInt;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 class RetraceFrameResultImpl implements RetraceFrameResult {
 
   private final RetraceClassResultImpl classResult;
   private final MethodDefinition methodDefinition;
-  private final Optional<Integer> obfuscatedPosition;
+  private final OptionalInt obfuscatedPosition;
   private final List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappedRanges;
   private final RetracerImpl retracer;
+  private final RetraceStackTraceContextImpl context;
 
   private OptionalBool isAmbiguousCache = OptionalBool.UNKNOWN;
 
@@ -44,13 +45,15 @@
       RetraceClassResultImpl classResult,
       List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappedRanges,
       MethodDefinition methodDefinition,
-      Optional<Integer> obfuscatedPosition,
-      RetracerImpl retracer) {
+      OptionalInt obfuscatedPosition,
+      RetracerImpl retracer,
+      RetraceStackTraceContextImpl context) {
     this.classResult = classResult;
     this.methodDefinition = methodDefinition;
     this.obfuscatedPosition = obfuscatedPosition;
     this.mappedRanges = mappedRanges;
     this.retracer = retracer;
+    this.context = context;
   }
 
   @Override
@@ -95,7 +98,8 @@
                                 classElement.getRetracedClass().getClassReference())),
                         ImmutableList.of(),
                         obfuscatedPosition,
-                        retracer));
+                        retracer,
+                        context));
               }
               // Iterate over mapped ranges that may have different positions than specified.
               List<ElementImpl> ambiguousFrames = new ArrayList<>();
@@ -128,13 +132,12 @@
         getRetracedMethod(methodReference, topFrame, obfuscatedPosition),
         mappedRangesForElement,
         obfuscatedPosition,
-        retracer);
+        retracer,
+        context);
   }
 
   private RetracedMethodReferenceImpl getRetracedMethod(
-      MethodReference methodReference,
-      MappedRange mappedRange,
-      Optional<Integer> obfuscatedPosition) {
+      MethodReference methodReference, MappedRange mappedRange, OptionalInt obfuscatedPosition) {
     if (mappedRange.minifiedRange == null
         || (obfuscatedPosition.orElse(-1) == -1 && !isAmbiguous())) {
       int originalLineNumber = mappedRange.getFirstLineNumberOfOriginalRange();
@@ -145,11 +148,11 @@
       }
     }
     if (!obfuscatedPosition.isPresent()
-        || !mappedRange.minifiedRange.contains(obfuscatedPosition.get())) {
+        || !mappedRange.minifiedRange.contains(obfuscatedPosition.getAsInt())) {
       return RetracedMethodReferenceImpl.create(methodReference);
     }
     return RetracedMethodReferenceImpl.create(
-        methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition.get()));
+        methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition.getAsInt()));
   }
 
   public static class ElementImpl implements RetraceFrameElement {
@@ -158,22 +161,25 @@
     private final RetraceFrameResultImpl retraceFrameResult;
     private final RetraceClassElementImpl classElement;
     private final List<MappedRange> mappedRanges;
-    private final Optional<Integer> obfuscatedPosition;
+    private final OptionalInt obfuscatedPosition;
     private final RetracerImpl retracer;
+    private final RetraceStackTraceContextImpl context;
 
     ElementImpl(
         RetraceFrameResultImpl retraceFrameResult,
         RetraceClassElementImpl classElement,
         RetracedMethodReferenceImpl methodReference,
         List<MappedRange> mappedRanges,
-        Optional<Integer> obfuscatedPosition,
-        RetracerImpl retracer) {
+        OptionalInt obfuscatedPosition,
+        RetracerImpl retracer,
+        RetraceStackTraceContextImpl context) {
       this.methodReference = methodReference;
       this.retraceFrameResult = retraceFrameResult;
       this.classElement = classElement;
       this.mappedRanges = mappedRanges;
       this.obfuscatedPosition = obfuscatedPosition;
       this.retracer = retracer;
+      this.context = context;
     }
 
     private boolean isOuterMostFrameCompilerSynthesized() {
@@ -214,46 +220,67 @@
     }
 
     @Override
-    public void visitAllFrames(BiConsumer<RetracedMethodReference, Integer> consumer) {
+    public void forEach(Consumer<RetracedSingleFrame> consumer) {
+      if (mappedRanges == null || mappedRanges.isEmpty()) {
+        consumer.accept(RetracedSingleFrameImpl.create(getTopFrame(), 0));
+        return;
+      }
       int counter = 0;
-      consumer.accept(getTopFrame(), counter++);
+      consumer.accept(RetracedSingleFrameImpl.create(getTopFrame(), counter++));
       for (RetracedMethodReferenceImpl outerFrame : getOuterFrames()) {
-        consumer.accept(outerFrame, counter++);
+        consumer.accept(RetracedSingleFrameImpl.create(outerFrame, counter++));
       }
     }
 
     @Override
-    public void visitRewrittenFrames(
-        RetraceStackTraceContext context, BiConsumer<RetracedMethodReference, Integer> consumer) {
+    public Stream<RetracedSingleFrame> stream() {
+      Stream.Builder<RetracedSingleFrame> builder = Stream.builder();
+      forEach(builder::add);
+      return builder.build();
+    }
+
+    @Override
+    public void forEachRewritten(
+        RetraceStackTraceContext context, Consumer<RetracedSingleFrame> consumer) {
       RetraceStackTraceContextImpl contextImpl = (RetraceStackTraceContextImpl) context;
       RetraceStackTraceCurrentEvaluationInformation currentFrameInformation =
-          contextImpl.computeRewritingInformation(mappedRanges);
+          context == null
+              ? RetraceStackTraceCurrentEvaluationInformation.empty()
+              : contextImpl.computeRewritingInformation(mappedRanges);
       int index = 0;
       int numberOfFramesToRemove = currentFrameInformation.getRemoveInnerFrames();
-      RetracedMethodReferenceImpl prev = getTopFrame();
-      List<RetracedMethodReferenceImpl> outerFrames = getOuterFrames();
-      if (numberOfFramesToRemove > outerFrames.size() + 1) {
-        assert prev.isKnown();
+      int totalNumberOfFrames =
+          (mappedRanges == null || mappedRanges.isEmpty()) ? 1 : mappedRanges.size();
+      if (numberOfFramesToRemove > totalNumberOfFrames) {
         DiagnosticsHandler diagnosticsHandler = retracer.getDiagnosticsHandler();
         diagnosticsHandler.warning(
             RetraceInvalidRewriteFrameDiagnostics.create(
-                numberOfFramesToRemove, prev.asKnown().toString()));
+                numberOfFramesToRemove, getTopFrame().asKnown().toString()));
         numberOfFramesToRemove = 0;
       }
+      RetracedMethodReferenceImpl prev = getTopFrame();
+      List<RetracedMethodReferenceImpl> outerFrames = getOuterFrames();
       for (RetracedMethodReferenceImpl next : outerFrames) {
         if (numberOfFramesToRemove-- <= 0) {
-          consumer.accept(prev, index++);
+          consumer.accept(RetracedSingleFrameImpl.create(prev, index++));
         }
         prev = next;
       }
       // We expect only the last frame, i.e., the outer-most caller to potentially be synthesized.
       // If not include it too.
       if (numberOfFramesToRemove <= 0 && !isOuterMostFrameCompilerSynthesized()) {
-        consumer.accept(prev, index);
+        consumer.accept(RetracedSingleFrameImpl.create(prev, index));
       }
     }
 
     @Override
+    public Stream<RetracedSingleFrame> streamRewritten(RetraceStackTraceContext context) {
+      Stream.Builder<RetracedSingleFrame> builder = Stream.builder();
+      forEachRewritten(context, builder::add);
+      return builder.build();
+    }
+
+    @Override
     public RetracedSourceFile getSourceFile(RetracedClassMemberReference frame) {
       return RetraceUtils.getSourceFileOrLookup(
           frame.getHolderClass(), classElement, retraceFrameResult.retracer);
@@ -266,20 +293,22 @@
       }
       List<RetracedMethodReferenceImpl> outerFrames = new ArrayList<>();
       for (int i = 1; i < mappedRanges.size(); i++) {
-        MappedRange mappedRange = mappedRanges.get(i);
-        MethodReference methodReference =
-            methodReferenceFromMappedRange(
-                mappedRange, classElement.getRetracedClass().getClassReference());
-        outerFrames.add(
-            retraceFrameResult.getRetracedMethod(methodReference, mappedRange, obfuscatedPosition));
+        outerFrames.add(getMethodReferenceFromMappedRange(mappedRanges.get(i)));
       }
       return outerFrames;
     }
 
+    private RetracedMethodReferenceImpl getMethodReferenceFromMappedRange(MappedRange mappedRange) {
+      MethodReference methodReference =
+          methodReferenceFromMappedRange(
+              mappedRange, classElement.getRetracedClass().getClassReference());
+      return retraceFrameResult.getRetracedMethod(methodReference, mappedRange, obfuscatedPosition);
+    }
+
     @Override
     public RetraceStackTraceContext getContext() {
       // This will change when supporting outline frames.
-      return RetraceStackTraceContext.getInitialContext();
+      return RetraceStackTraceContext.empty();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index 7d97ed2..978a933 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.retrace.RetraceMethodElement;
 import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
 import com.android.tools.r8.retrace.RetracedSourceFile;
 import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
@@ -17,7 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
+import java.util.OptionalInt;
 import java.util.stream.Stream;
 
 public class RetraceMethodResultImpl implements RetraceMethodResult {
@@ -61,7 +62,7 @@
   }
 
   @Override
-  public RetraceFrameResultImpl narrowByPosition(int position) {
+  public RetraceFrameResultImpl narrowByPosition(RetraceStackTraceContext context, int position) {
     List<Pair<RetraceClassElementImpl, List<MappedRange>>> narrowedRanges = new ArrayList<>();
     List<Pair<RetraceClassElementImpl, List<MappedRange>>> noMappingRanges = new ArrayList<>();
     for (Pair<RetraceClassElementImpl, List<MappedRange>> mappedRange : mappedRanges) {
@@ -92,8 +93,9 @@
         classResult,
         narrowedRanges.isEmpty() ? noMappingRanges : narrowedRanges,
         methodDefinition,
-        Optional.of(position),
-        retracer);
+        OptionalInt.of(position),
+        retracer,
+        (RetraceStackTraceContextImpl) context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
index 7cc4a30..31533d4 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
@@ -15,14 +15,14 @@
 
 public class RetraceStackTraceContextImpl implements RetraceStackTraceContext {
 
-  private final ClassReference seenException;
+  private final ClassReference thrownException;
 
-  private RetraceStackTraceContextImpl(ClassReference seenException) {
-    this.seenException = seenException;
+  private RetraceStackTraceContextImpl(ClassReference thrownException) {
+    this.thrownException = thrownException;
   }
 
-  public ClassReference getSeenException() {
-    return seenException;
+  public ClassReference getThrownException() {
+    return thrownException;
   }
 
   RetraceStackTraceCurrentEvaluationInformation computeRewritingInformation(
@@ -54,22 +54,26 @@
   }
 
   public static Builder builder() {
-    return new Builder();
+    return Builder.create();
   }
 
   public static class Builder {
 
-    private ClassReference seenException;
+    private ClassReference thrownException;
 
     private Builder() {}
 
-    public Builder setSeenException(ClassReference seenException) {
-      this.seenException = seenException;
+    public Builder setThrownException(ClassReference thrownException) {
+      this.thrownException = thrownException;
       return this;
     }
 
     public RetraceStackTraceContextImpl build() {
-      return new RetraceStackTraceContextImpl(seenException);
+      return new RetraceStackTraceContextImpl(thrownException);
+    }
+
+    public static Builder create() {
+      return new Builder();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
new file mode 100644
index 0000000..aa2c1f0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
+import com.android.tools.r8.retrace.RetraceThrownExceptionResult;
+import com.android.tools.r8.retrace.RetracedSourceFile;
+import java.util.stream.Stream;
+
+public class RetraceThrownExceptionResultImpl implements RetraceThrownExceptionResult {
+
+  private final RetraceStackTraceContextImpl context;
+  private final ClassReference obfuscatedReference;
+  private final ClassNamingForNameMapper mapper;
+
+  RetraceThrownExceptionResultImpl(
+      RetraceStackTraceContextImpl context,
+      ClassReference obfuscatedReference,
+      ClassNamingForNameMapper mapper) {
+    this.context = context;
+    this.obfuscatedReference = obfuscatedReference;
+    this.mapper = mapper;
+  }
+
+  @Override
+  public Stream<RetraceThrownExceptionElement> stream() {
+    return Stream.of(createElement());
+  }
+
+  private RetraceThrownExceptionElement createElement() {
+    return new RetraceThrownExceptionElementImpl(
+        this,
+        RetracedClassReferenceImpl.create(
+            mapper == null
+                ? obfuscatedReference
+                : Reference.classFromTypeName(mapper.originalName)),
+        mapper,
+        obfuscatedReference);
+  }
+
+  public static class RetraceThrownExceptionElementImpl implements RetraceThrownExceptionElement {
+
+    private final RetraceThrownExceptionResultImpl thrownExceptionResult;
+    private final RetracedClassReferenceImpl classReference;
+    private final ClassNamingForNameMapper mapper;
+    private final ClassReference thrownException;
+
+    private RetraceThrownExceptionElementImpl(
+        RetraceThrownExceptionResultImpl thrownExceptionResult,
+        RetracedClassReferenceImpl classReference,
+        ClassNamingForNameMapper mapper,
+        ClassReference thrownException) {
+      this.thrownExceptionResult = thrownExceptionResult;
+      this.classReference = classReference;
+      this.mapper = mapper;
+      this.thrownException = thrownException;
+    }
+
+    @Override
+    public RetracedClassReferenceImpl getRetracedClass() {
+      return classReference;
+    }
+
+    @Override
+    public RetraceThrownExceptionResult getRetraceResultContext() {
+      return thrownExceptionResult;
+    }
+
+    @Override
+    public RetracedSourceFile getSourceFile() {
+      if (mapper != null) {
+        for (MappingInformation info : mapper.getAdditionalMappingInfo()) {
+          if (info.isFileNameInformation()) {
+            return new RetracedSourceFileImpl(info.asFileNameInformation().getFileName());
+          }
+        }
+      }
+      return new RetracedSourceFileImpl(null);
+    }
+
+    @Override
+    public boolean isCompilerSynthesized() {
+      return false;
+    }
+
+    @Override
+    public RetraceStackTraceContext getContext() {
+      return RetraceStackTraceContextImpl.builder().setThrownException(thrownException).build();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
index 9ac65be..49061b2 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
@@ -16,6 +16,23 @@
 public abstract class RetracedMethodReferenceImpl implements RetracedMethodReference {
 
   private static final int NO_POSITION = -1;
+  private static final Comparator<RetracedMethodReference> comparator =
+      Comparator.comparing(RetracedMethodReference::getMethodName)
+          .thenComparing(RetracedMethodReference::isKnown)
+          .thenComparing(
+              RetracedMethodReference::asKnown,
+              Comparator.nullsFirst(
+                      Comparator.comparing(
+                          (KnownRetracedMethodReference m) -> {
+                            if (m == null) {
+                              return null;
+                            }
+                            return m.isVoid() ? "void" : m.getReturnType().getTypeName();
+                          }))
+                  .thenComparing(
+                      KnownRetracedMethodReference::getFormalTypes,
+                      ComparatorUtils.listComparator(
+                          Comparator.comparing(TypeReference::getTypeName))));
 
   private RetracedMethodReferenceImpl() {}
 
@@ -36,23 +53,7 @@
 
   @Override
   public int compareTo(RetracedMethodReference other) {
-    return Comparator.comparing(RetracedMethodReference::getMethodName)
-        .thenComparing(RetracedMethodReference::isKnown)
-        .thenComparing(
-            RetracedMethodReference::asKnown,
-            Comparator.nullsFirst(
-                    Comparator.comparing(
-                        (KnownRetracedMethodReference m) -> {
-                          if (m == null) {
-                            return null;
-                          }
-                          return m.isVoid() ? "void" : m.getReturnType().getTypeName();
-                        }))
-                .thenComparing(
-                    KnownRetracedMethodReference::getFormalTypes,
-                    ComparatorUtils.listComparator(
-                        Comparator.comparing(TypeReference::getTypeName))))
-        .compare(this, other);
+    return comparator.compare(this, other);
   }
 
   public static final class KnownRetracedMethodReferenceImpl extends RetracedMethodReferenceImpl
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedSingleFrameImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedSingleFrameImpl.java
new file mode 100644
index 0000000..c69b148
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedSingleFrameImpl.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
+
+public class RetracedSingleFrameImpl implements RetracedSingleFrame {
+
+  private final RetracedMethodReference methodReference;
+  private final int index;
+
+  private RetracedSingleFrameImpl(RetracedMethodReference methodReference, int index) {
+    this.methodReference = methodReference;
+    this.index = index;
+  }
+
+  @Override
+  public RetracedMethodReference getMethodReference() {
+    return methodReference;
+  }
+
+  @Override
+  public int getIndex() {
+    return index;
+  }
+
+  static RetracedSingleFrameImpl create(RetracedMethodReference methodReference, int index) {
+    return new RetracedSingleFrameImpl(methodReference, index);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index bad33d8..9aadbb3 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -63,7 +63,7 @@
 
   @Override
   public RetraceFrameResult retraceFrame(MethodReference methodReference, int position) {
-    return retraceFrame(methodReference, position, RetraceStackTraceContext.getInitialContext());
+    return retraceFrame(methodReference, position, RetraceStackTraceContext.empty());
   }
 
   @Override
@@ -71,7 +71,7 @@
       MethodReference methodReference, int position, RetraceStackTraceContext context) {
     return retraceClass(methodReference.getHolderClass())
         .lookupMethod(methodReference.getMethodName())
-        .narrowByPosition(position);
+        .narrowByPosition(context, position);
   }
 
   @Override
@@ -89,4 +89,10 @@
   public RetraceTypeResultImpl retraceType(TypeReference typeReference) {
     return RetraceTypeResultImpl.create(typeReference, this);
   }
+
+  @Override
+  public RetraceThrownExceptionResultImpl retraceThrownException(
+      ClassReference exception, RetraceStackTraceContext context) {
+    return retraceClass(exception).lookupThrownException(context);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index 2e2c38f..8040744 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -25,7 +25,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Optional;
+import java.util.OptionalInt;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -70,26 +70,29 @@
       RetraceClassResult classResult) {
     return currentResults.flatMap(
         proxy ->
-            classResult.stream()
+            // We assume, since no method was defined for this stack trace element, that this was a
+            // thrown exception.
+            classResult.lookupThrownException(proxy.getContext()).stream()
                 .map(
-                    classElement ->
+                    thrownExceptionElement ->
                         proxy
                             .builder()
-                            .setRetracedClass(classElement.getRetracedClass())
+                            .setRetracedClass(thrownExceptionElement.getRetracedClass())
                             .joinAmbiguous(classResult.isAmbiguous())
                             .setTopFrame(true)
-                            // We assume, since no method was defined for this stack trace element,
-                            // that this was a thrown exception.
-                            .setContext(classElement.getContextWhereClassWasThrown())
+                            .setContext(thrownExceptionElement.getContext())
                             .applyIf(
                                 element.hasSourceFile(),
                                 builder -> {
-                                  RetracedSourceFile sourceFile = classElement.getSourceFile();
+                                  RetracedSourceFile sourceFile =
+                                      thrownExceptionElement.getSourceFile();
                                   builder.setSourceFile(
                                       sourceFile.hasRetraceResult()
                                           ? sourceFile.getSourceFile()
                                           : RetraceUtils.inferSourceFile(
-                                              classElement.getRetracedClass().getTypeName(),
+                                              thrownExceptionElement
+                                                  .getRetracedClass()
+                                                  .getTypeName(),
                                               element.getSourceFile(),
                                               classResult.hasRetraceResult()));
                                 })
@@ -105,49 +108,48 @@
           RetraceFrameResult frameResult =
               classResult.lookupFrame(
                   proxy.context,
-                  element.hasLineNumber() ? Optional.of(element.getLineNumber()) : Optional.empty(),
+                  element.hasLineNumber()
+                      ? OptionalInt.of(element.getLineNumber())
+                      : OptionalInt.empty(),
                   element.getMethodName());
           return frameResult.stream()
               .flatMap(
-                  frameElement -> {
-                    List<RetraceStackTraceElementProxyImpl<T, ST>> retracedProxies =
-                        new ArrayList<>();
-                    frameElement.visitRewrittenFrames(
-                        proxy.getContext(),
-                        (frame, index) -> {
-                          boolean isTopFrame = index == 0;
-                          retracedProxies.add(
-                              proxy
-                                  .builder()
-                                  .setRetracedClass(frame.getHolderClass())
-                                  .setRetracedMethod(frame)
-                                  .joinAmbiguous(frameResult.isAmbiguous() && isTopFrame)
-                                  .setTopFrame(isTopFrame)
-                                  .setContext(frameElement.getContext())
-                                  .applyIf(
-                                      element.hasLineNumber(),
-                                      builder -> {
-                                        builder.setLineNumber(
-                                            frame.getOriginalPositionOrDefault(
-                                                element.getLineNumber()));
-                                      })
-                                  .applyIf(
-                                      element.hasSourceFile(),
-                                      builder -> {
-                                        RetracedSourceFile sourceFileResult =
-                                            frameElement.getSourceFile(frame);
-                                        builder.setSourceFile(
-                                            sourceFileResult.hasRetraceResult()
-                                                ? sourceFileResult.getSourceFile()
-                                                : RetraceUtils.inferSourceFile(
-                                                    frame.getHolderClass().getTypeName(),
-                                                    element.getSourceFile(),
-                                                    classResult.hasRetraceResult()));
-                                      })
-                                  .build());
-                        });
-                    return retracedProxies.stream();
-                  });
+                  frameElement ->
+                      frameElement
+                          .streamRewritten(proxy.getContext())
+                          .map(
+                              singleFrame -> {
+                                boolean isTopFrame = singleFrame.getIndex() == 0;
+                                RetracedMethodReference method = singleFrame.getMethodReference();
+                                return proxy
+                                    .builder()
+                                    .setRetracedClass(method.getHolderClass())
+                                    .setRetracedMethod(method)
+                                    .joinAmbiguous(frameResult.isAmbiguous() && isTopFrame)
+                                    .setTopFrame(isTopFrame)
+                                    .setContext(frameElement.getContext())
+                                    .applyIf(
+                                        element.hasLineNumber(),
+                                        builder -> {
+                                          builder.setLineNumber(
+                                              method.getOriginalPositionOrDefault(
+                                                  element.getLineNumber()));
+                                        })
+                                    .applyIf(
+                                        element.hasSourceFile(),
+                                        builder -> {
+                                          RetracedSourceFile sourceFileResult =
+                                              frameElement.getSourceFile(method);
+                                          builder.setSourceFile(
+                                              sourceFileResult.hasRetraceResult()
+                                                  ? sourceFileResult.getSourceFile()
+                                                  : RetraceUtils.inferSourceFile(
+                                                      method.getHolderClass().getTypeName(),
+                                                      element.getSourceFile(),
+                                                      classResult.hasRetraceResult()));
+                                        })
+                                    .build();
+                              }));
         });
   }
 
@@ -330,12 +332,12 @@
     }
 
     @Override
-    public boolean hasFieldOrReturnType() {
+    public boolean hasRetracedFieldOrReturnType() {
       return fieldOrReturnType != null;
     }
 
     @Override
-    public boolean hasMethodArguments() {
+    public boolean hasRetracedMethodArguments() {
       return methodArguments != null;
     }
 
@@ -365,7 +367,7 @@
     }
 
     @Override
-    public List<RetracedTypeReference> getMethodArguments() {
+    public List<RetracedTypeReference> getRetracedMethodArguments() {
       return methodArguments;
     }
 
@@ -408,6 +410,9 @@
 
     @Override
     public int compareTo(RetraceStackTraceElementProxy<T, ST> other) {
+      if (this == other) {
+        return 0;
+      }
       int classCompare = Boolean.compare(hasRetracedClass(), other.hasRetracedClass());
       if (classCompare != 0) {
         return classCompare;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index 7afc10b..fc195d0 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -273,7 +273,7 @@
               startIndex,
               endIndex,
               (retraced, original, verbose) -> {
-                if (!retraced.hasFieldOrReturnType()) {
+                if (!retraced.hasRetracedFieldOrReturnType()) {
                   return original.getFieldOrReturnType();
                 }
                 return retraced.getRetracedFieldOrReturnType().isVoid()
@@ -291,11 +291,11 @@
               startIndex,
               endIndex,
               (retraced, original, verbose) -> {
-                if (!retraced.hasMethodArguments()) {
+                if (!retraced.hasRetracedMethodArguments()) {
                   return original.getMethodArguments();
                 }
                 return StringUtils.join(
-                    ",", retraced.getMethodArguments(), RetracedTypeReference::getTypeName);
+                    ",", retraced.getRetracedMethodArguments(), RetracedTypeReference::getTypeName);
               });
       orderedIndices.add(methodArguments);
       return this;
diff --git a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
index aca76da..8e48dfe 100644
--- a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
@@ -21,8 +21,8 @@
 import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.TriConsumer;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 
@@ -31,12 +31,7 @@
   private final Map<EnqueuerEvent, MinimumKeepInfoCollection> dependentMinimumKeepInfo;
 
   public DependentMinimumKeepInfoCollection() {
-    this(new HashMap<>());
-  }
-
-  private DependentMinimumKeepInfoCollection(
-      Map<EnqueuerEvent, MinimumKeepInfoCollection> dependentMinimumKeepInfo) {
-    this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
+    this.dependentMinimumKeepInfo = new ConcurrentHashMap<>();
   }
 
   public void forEach(BiConsumer<EnqueuerEvent, MinimumKeepInfoCollection> consumer) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index b04748c..dfc170e 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -3,40 +3,86 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public class DiscardedChecker {
 
-  private final Set<DexReference> checkDiscarded;
-  private final Iterable<DexProgramClass> classes;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final InternalOptions options;
 
-  public DiscardedChecker(RootSet rootSet, Iterable<DexProgramClass> classes) {
-    this.checkDiscarded = new HashSet<>(rootSet.checkDiscarded);
-    this.classes = classes;
+  private final List<ProgramDefinition> failed = new ArrayList<>();
+
+  private DiscardedChecker(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.appView = appView;
+    this.options = appView.options();
   }
 
-  public List<DexDefinition> run() {
-    List<DexDefinition> failed = new ArrayList<>(checkDiscarded.size());
-    // TODO(b/131668850): Lookup the definition based on the reference.
-    for (DexProgramClass clazz : classes) {
-      checkItem(clazz, failed);
-      clazz.forEachMethod(method -> checkItem(method, failed));
-      clazz.forEachField(field -> checkItem(field, failed));
-    }
+  public static DiscardedChecker create(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return new DiscardedChecker(appView);
+  }
+
+  public static DiscardedChecker createForMainDex(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    MinimumKeepInfoCollection unconditionalKeepInfo =
+        appView
+            .getMainDexRootSet()
+            .getDependentMinimumKeepInfo()
+            .getOrCreateUnconditionalMinimumKeepInfo();
+    return new DiscardedChecker(appView) {
+
+      @Override
+      boolean isCheckDiscardedEnabled(ProgramDefinition definition) {
+        return unconditionalKeepInfo.hasMinimumKeepInfoThatMatches(
+            definition.getReference(), Joiner::isCheckDiscardedEnabled);
+      }
+    };
+  }
+
+  public List<ProgramDefinition> run(
+      Iterable<DexProgramClass> classes, ExecutorService executorService)
+      throws ExecutionException {
+    assert failed.isEmpty();
+
+    // TODO(b/131668850): Consider only iterating the items matched by a -checkdiscard rule.
+    ThreadUtils.processItems(classes, this::checkClassAndMembers, executorService);
+
+    // Sort the failures for determinism.
+    failed.sort((item, other) -> item.getReference().compareTo(other.getReference()));
+
     return failed;
   }
 
-  private void checkItem(DexDefinition item, List<DexDefinition> failed) {
-    DexReference reference = item.getReference();
-    if (checkDiscarded.contains(reference)) {
-      failed.add(item);
+  boolean isCheckDiscardedEnabled(ProgramDefinition definition) {
+    return appView.getKeepInfo().getInfo(definition).isCheckDiscardedEnabled(options);
+  }
+
+  private void checkClassAndMembers(DexProgramClass clazz) {
+    // Only look for -checkdiscard failures for members if the class itself did not fail a
+    // -checkdiscard check
+    if (check(clazz)) {
+      clazz.forEachProgramMember(this::check);
     }
   }
+
+  /** Returns true if the check succeeded (i.e., no -checkdiscard failure was found). */
+  private boolean check(ProgramDefinition item) {
+    if (isCheckDiscardedEnabled(item)) {
+      // We expect few check discarded failures thus locking here should be OK.
+      synchronized (failed) {
+        failed.add(item);
+      }
+      return false;
+    }
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 28c2602..b4fef63 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -4098,10 +4098,9 @@
       // When generating interface bridges the method may be inserted into a live hierarchy.
       // If so we need to also mark it as live as the reachable check above will not reprocess the
       // hierarchy.
-      // TODO(b/183998768): The check for isInterface here should be possible to remove now.
       if (definition.isNonAbstractVirtualMethod()
-          && (objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(holder)
-              || holder.isInterface())) {
+          && objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(
+              holder)) {
         // TODO(b/120959039): Codify the kept-graph expectations for these cases in tests.
         markVirtualMethodAsLive(target, reason);
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index f846462..12265aa 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -20,6 +20,7 @@
   private final boolean allowMinification;
   private final boolean allowOptimization;
   private final boolean allowShrinking;
+  private final boolean checkDiscarded;
   private final boolean requireAccessModificationForRepackaging;
 
   private KeepInfo(
@@ -28,12 +29,14 @@
       boolean allowMinification,
       boolean allowOptimization,
       boolean allowShrinking,
+      boolean checkDiscarded,
       boolean requireAccessModificationForRepackaging) {
     this.allowAccessModification = allowAccessModification;
     this.allowAnnotationRemoval = allowAnnotationRemoval;
     this.allowMinification = allowMinification;
     this.allowOptimization = allowOptimization;
     this.allowShrinking = allowShrinking;
+    this.checkDiscarded = checkDiscarded;
     this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
   }
 
@@ -44,6 +47,7 @@
         builder.isMinificationAllowed(),
         builder.isOptimizationAllowed(),
         builder.isShrinkingAllowed(),
+        builder.isCheckDiscardedEnabled(),
         builder.isAccessModificationRequiredForRepackaging());
   }
 
@@ -70,8 +74,18 @@
     return allowAnnotationRemoval;
   }
 
+  public boolean isCheckDiscardedEnabled(GlobalKeepInfoConfiguration configuration) {
+    return internalIsCheckDiscardedEnabled();
+  }
+
+  boolean internalIsCheckDiscardedEnabled() {
+    return checkDiscarded;
+  }
+
   public boolean isParameterRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
-    return isOptimizationAllowed(configuration) && isShrinkingAllowed(configuration);
+    return isOptimizationAllowed(configuration)
+        && isShrinkingAllowed(configuration)
+        && !isCheckDiscardedEnabled(configuration);
   }
 
   /**
@@ -208,7 +222,8 @@
         && (allowAnnotationRemoval || !other.internalIsAnnotationRemovalAllowed())
         && (allowMinification || !other.internalIsMinificationAllowed())
         && (allowOptimization || !other.internalIsOptimizationAllowed())
-        && (allowShrinking || !other.internalIsShrinkingAllowed());
+        && (allowShrinking || !other.internalIsShrinkingAllowed())
+        && (!checkDiscarded || other.internalIsCheckDiscardedEnabled());
   }
 
   /** Builder to construct an arbitrary keep info object. */
@@ -230,6 +245,7 @@
     private boolean allowMinification;
     private boolean allowOptimization;
     private boolean allowShrinking;
+    private boolean checkDiscarded;
     private boolean requireAccessModificationForRepackaging;
 
     Builder() {
@@ -243,6 +259,7 @@
       allowMinification = original.internalIsMinificationAllowed();
       allowOptimization = original.internalIsOptimizationAllowed();
       allowShrinking = original.internalIsShrinkingAllowed();
+      checkDiscarded = original.internalIsCheckDiscardedEnabled();
       requireAccessModificationForRepackaging =
           original.internalIsAccessModificationRequiredForRepackaging();
     }
@@ -253,6 +270,7 @@
       disallowMinification();
       disallowOptimization();
       disallowShrinking();
+      unsetCheckDiscarded();
       requireAccessModificationForRepackaging();
       return self();
     }
@@ -263,6 +281,7 @@
       allowMinification();
       allowOptimization();
       allowShrinking();
+      unsetCheckDiscarded();
       unsetRequireAccessModificationForRepackaging();
       return self();
     }
@@ -288,6 +307,7 @@
           && isMinificationAllowed() == other.internalIsMinificationAllowed()
           && isOptimizationAllowed() == other.internalIsOptimizationAllowed()
           && isShrinkingAllowed() == other.internalIsShrinkingAllowed()
+          && isCheckDiscardedEnabled() == other.internalIsCheckDiscardedEnabled()
           && isAccessModificationRequiredForRepackaging()
               == other.internalIsAccessModificationRequiredForRepackaging();
     }
@@ -304,6 +324,10 @@
       return allowAnnotationRemoval;
     }
 
+    public boolean isCheckDiscardedEnabled() {
+      return checkDiscarded;
+    }
+
     public boolean isMinificationAllowed() {
       return allowMinification;
     }
@@ -355,6 +379,19 @@
       return setAllowShrinking(false);
     }
 
+    public B setCheckDiscarded(boolean checkDiscarded) {
+      this.checkDiscarded = checkDiscarded;
+      return self();
+    }
+
+    public B setCheckDiscarded() {
+      return setCheckDiscarded(true);
+    }
+
+    public B unsetCheckDiscarded() {
+      return setCheckDiscarded(false);
+    }
+
     public B setRequireAccessModificationForRepackaging(
         boolean requireAccessModificationForRepackaging) {
       this.requireAccessModificationForRepackaging = requireAccessModificationForRepackaging;
@@ -440,6 +477,10 @@
       return builder.isEqualTo(builder.getBottomInfo());
     }
 
+    public boolean isCheckDiscardedEnabled() {
+      return builder.isCheckDiscardedEnabled();
+    }
+
     public boolean isShrinkingAllowed() {
       return builder.isShrinkingAllowed();
     }
@@ -483,6 +524,11 @@
       return self();
     }
 
+    public J setCheckDiscarded() {
+      builder.setCheckDiscarded();
+      return self();
+    }
+
     public J requireAccessModificationForRepackaging() {
       builder.requireAccessModificationForRepackaging();
       return self();
@@ -495,6 +541,7 @@
       applyIf(!builder.isMinificationAllowed(), Joiner::disallowMinification);
       applyIf(!builder.isOptimizationAllowed(), Joiner::disallowOptimization);
       applyIf(!builder.isShrinkingAllowed(), Joiner::disallowShrinking);
+      applyIf(builder.isCheckDiscardedEnabled(), Joiner::setCheckDiscarded);
       applyIf(
           builder.isAccessModificationRequiredForRepackaging(),
           Joiner::requireAccessModificationForRepackaging);
diff --git a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
index 9f97e63..ec1594a 100644
--- a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
@@ -20,8 +20,8 @@
 import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.utils.MapUtils;
 import java.util.Collections;
-import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
 import java.util.function.BiPredicate;
 import java.util.function.Predicate;
@@ -34,7 +34,7 @@
   private final Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo;
 
   public MinimumKeepInfoCollection() {
-    this(new IdentityHashMap<>());
+    this(new ConcurrentHashMap<>());
   }
 
   private MinimumKeepInfoCollection(Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
index 82b81a4..8ab748e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -75,6 +75,16 @@
   }
 
   @Override
+  public boolean isProguardCheckDiscardRule() {
+    return true;
+  }
+
+  @Override
+  public ProguardCheckDiscardRule asProguardCheckDiscardRule() {
+    return this;
+  }
+
+  @Override
   String typeString() {
     return "checkdiscard";
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index fbedd8e..41181b1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -66,6 +66,14 @@
     used = true;
   }
 
+  public boolean isProguardCheckDiscardRule() {
+    return false;
+  }
+
+  public ProguardCheckDiscardRule asProguardCheckDiscardRule() {
+    return null;
+  }
+
   public boolean isProguardKeepRule() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index c4ddd9f..3dc1bc5 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
+import static com.google.common.base.Predicates.alwaysTrue;
 import static java.util.Collections.emptyMap;
 
 import com.android.tools.r8.dex.Constants;
@@ -38,6 +39,7 @@
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -53,6 +55,7 @@
 import com.android.tools.r8.shaking.EnqueuerEvent.InstantiatedClassEnqueuerEvent;
 import com.android.tools.r8.shaking.EnqueuerEvent.LiveClassEnqueuerEvent;
 import com.android.tools.r8.shaking.EnqueuerEvent.UnconditionalKeepInfoEvent;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -60,6 +63,7 @@
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
@@ -104,7 +108,6 @@
     private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo =
         new DependentMinimumKeepInfoCollection();
     private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
-    private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
     private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
     private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
     private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
@@ -136,6 +139,7 @@
 
     private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings =
         new LinkedHashMap<>();
+    private final Set<DexProgramClass> classesWithCheckDiscardedMembers = Sets.newIdentityHashSet();
 
     private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
 
@@ -229,7 +233,7 @@
             } else {
               // Members mentioned at -keep should always be pinned as long as that -keep rule is
               // not triggered conditionally.
-              preconditionSupplier.put((definition -> true), null);
+              preconditionSupplier.put(alwaysTrue(), null);
             }
             markMatchingVisibleMethods(
                 clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
@@ -245,22 +249,8 @@
       assert ifRule == null;
       if (rule instanceof ProguardIfRule) {
         throw new Unreachable("-if rule will be evaluated separately, not here.");
-      } else if (rule instanceof ProguardCheckDiscardRule) {
-        if (!clazz.isProgramClass()) {
-          appView
-              .reporter()
-              .warning(
-                  new StringDiagnostic(
-                      "The rule `" + rule + "` matches a class not in the program."));
-        } else if (memberKeepRules.isEmpty()) {
-          markClass(clazz, rule, ifRule);
-        } else {
-          preconditionSupplier = ImmutableMap.of((definition -> true), clazz.asProgramClass());
-          markMatchingVisibleMethods(
-              clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
-          markMatchingVisibleFields(
-              clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
-        }
+      } else if (rule.isProguardCheckDiscardRule()) {
+        evaluateCheckDiscardRule(clazz, rule.asProguardCheckDiscardRule());
       } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
         markClass(clazz, rule, ifRule);
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
@@ -349,6 +339,7 @@
       } finally {
         application.timing.end();
       }
+      finalizeCheckDiscardedInformation();
       generateAssumeNoSideEffectsWarnings();
       if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
         BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
@@ -371,7 +362,6 @@
       return new RootSet(
           dependentMinimumKeepInfo,
           ImmutableList.copyOf(reasonAsked.values()),
-          ImmutableList.copyOf(checkDiscarded.values()),
           alwaysInline,
           neverInline,
           neverInlineDueToSingleCaller,
@@ -1180,9 +1170,10 @@
           assumedValues.put(item.asMember().getReference(), rule);
           context.markAsUsed();
         }
-      } else if (context instanceof ProguardCheckDiscardRule) {
-        checkDiscarded.computeIfAbsent(item.getReference(), i -> i);
-        context.markAsUsed();
+      } else if (context.isProguardCheckDiscardRule()) {
+        assert item.isProgramMember();
+        evaluateCheckDiscardMemberRule(
+            item.asProgramMember(), context.asProguardCheckDiscardRule());
       } else if (context instanceof InlineRule) {
         if (item.isMethod()) {
           DexMethod reference = item.asMethod().getReference();
@@ -1313,7 +1304,64 @@
       }
     }
 
-    private synchronized void evaluateKeepRule(
+    private void evaluateCheckDiscardRule(DexClass clazz, ProguardCheckDiscardRule rule) {
+      if (clazz.isProgramClass()) {
+        evaluateCheckDiscardRule(clazz.asProgramClass(), rule.asProguardCheckDiscardRule());
+      } else {
+        StringDiagnostic warning =
+            new StringDiagnostic("The rule `" + rule + "` matches a class not in the program.");
+        appView.reporter().warning(warning);
+      }
+    }
+
+    private void evaluateCheckDiscardRule(DexProgramClass clazz, ProguardCheckDiscardRule rule) {
+      if (rule.getMemberRules().isEmpty()) {
+        evaluateCheckDiscardClassAndAllMembersRule(clazz, rule);
+      } else if (clazz.hasFields() || clazz.hasMethods()) {
+        markMatchingFields(clazz, rule.getMemberRules(), rule, null, null);
+        markMatchingMethods(clazz, rule.getMemberRules(), rule, null, null);
+        classesWithCheckDiscardedMembers.add(clazz);
+      }
+    }
+
+    private void evaluateCheckDiscardClassAndAllMembersRule(
+        DexProgramClass clazz, ProguardCheckDiscardRule rule) {
+      setCheckDiscarded(clazz);
+      clazz.forEachProgramMember(this::setCheckDiscarded);
+      rule.markAsUsed();
+    }
+
+    private void evaluateCheckDiscardMemberRule(
+        ProgramMember<?, ?> member, ProguardCheckDiscardRule rule) {
+      setCheckDiscarded(member);
+      rule.markAsUsed();
+    }
+
+    private void setCheckDiscarded(ProgramDefinition definition) {
+      dependentMinimumKeepInfo
+          .getOrCreateUnconditionalMinimumKeepInfo()
+          .getOrCreateMinimumKeepInfoFor(definition.getReference())
+          .setCheckDiscarded();
+    }
+
+    private void finalizeCheckDiscardedInformation() {
+      MinimumKeepInfoCollection unconditionalKeepInfo =
+          dependentMinimumKeepInfo.getUnconditionalMinimumKeepInfoOrDefault(
+              MinimumKeepInfoCollection.empty());
+      for (DexProgramClass clazz : classesWithCheckDiscardedMembers) {
+        TraversalContinuation continueIfAllMembersMarkedAsCheckDiscarded =
+            clazz.traverseProgramMembers(
+                member ->
+                    TraversalContinuation.continueIf(
+                        unconditionalKeepInfo.hasMinimumKeepInfoThatMatches(
+                            member.getReference(), Joiner::isCheckDiscardedEnabled)));
+        if (continueIfAllMembersMarkedAsCheckDiscarded.shouldContinue()) {
+          setCheckDiscarded(clazz);
+        }
+      }
+    }
+
+    private void evaluateKeepRule(
         ProgramDefinition item,
         ProguardKeepRule context,
         ProguardMemberRule rule,
@@ -1553,7 +1601,6 @@
   public static class RootSet extends RootSetBase {
 
     public final ImmutableList<DexReference> reasonAsked;
-    public final ImmutableList<DexReference> checkDiscarded;
     public final Set<DexMethod> alwaysInline;
     public final Set<DexMethod> bypassClinitForInlining;
     public final Set<DexMethod> whyAreYouNotInlining;
@@ -1575,7 +1622,6 @@
     private RootSet(
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         ImmutableList<DexReference> reasonAsked,
-        ImmutableList<DexReference> checkDiscarded,
         Set<DexMethod> alwaysInline,
         Set<DexMethod> neverInline,
         Set<DexMethod> neverInlineDueToSingleCaller,
@@ -1608,7 +1654,6 @@
           delayedRootSetActionItems,
           pendingMethodMoveInverse);
       this.reasonAsked = reasonAsked;
-      this.checkDiscarded = checkDiscarded;
       this.alwaysInline = alwaysInline;
       this.bypassClinitForInlining = bypassClinitForInlining;
       this.whyAreYouNotInlining = whyAreYouNotInlining;
@@ -1865,7 +1910,6 @@
       StringBuilder builder = new StringBuilder();
       builder.append("RootSet");
       builder.append("\nreasonAsked: " + reasonAsked.size());
-      builder.append("\ncheckDiscarded: " + checkDiscarded.size());
       builder.append("\nnoSideEffects: " + noSideEffects.size());
       builder.append("\nassumedValues: " + assumedValues.size());
       builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
@@ -1959,7 +2003,6 @@
       return new MainDexRootSet(
           rootSet.getDependentMinimumKeepInfo(),
           rootSet.reasonAsked,
-          rootSet.checkDiscarded,
           rootSet.ifRules,
           rootSet.delayedRootSetActionItems);
     }
@@ -1970,13 +2013,11 @@
     public MainDexRootSet(
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         ImmutableList<DexReference> reasonAsked,
-        ImmutableList<DexReference> checkDiscarded,
         Set<ProguardIfRule> ifRules,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
           dependentMinimumKeepInfo,
           reasonAsked,
-          checkDiscarded,
           Collections.emptySet(),
           Collections.emptySet(),
           Collections.emptySet(),
@@ -2019,11 +2060,6 @@
         return this;
       }
 
-      ImmutableList.Builder<DexReference> rewrittenCheckDiscarded = ImmutableList.builder();
-      checkDiscarded.forEach(
-          reference ->
-              rewriteAndApplyIfNotPrimitiveType(
-                  graphLens, reference, rewrittenCheckDiscarded::add));
       ImmutableList.Builder<DexReference> rewrittenReasonAsked = ImmutableList.builder();
       reasonAsked.forEach(
           reference ->
@@ -2036,7 +2072,6 @@
       return new MainDexRootSet(
           getDependentMinimumKeepInfo().rewrittenWithLens(graphLens),
           rewrittenReasonAsked.build(),
-          rewrittenCheckDiscarded.build(),
           ifRules,
           delayedRootSetActionItems);
     }
@@ -2053,7 +2088,6 @@
       return new MainDexRootSet(
           getDependentMinimumKeepInfo(),
           reasonAsked,
-          checkDiscarded,
           ifRules,
           delayedRootSetActionItems);
     }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 2b8affa..e407c3a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -717,9 +717,6 @@
       builder.setName(methodReference.getName());
       builder.setProto(methodReference.getProto());
       buildMethodCallback.accept(builder);
-      // TODO(b/183998768): Make this safe for recursive definitions.
-      //  For example, the builder should be split into the creation of the method structure
-      //  and the creation of the method code. The code can then be constructed outside the lock.
       methodDefinition = builder.build();
       methodCollection.addMethod(methodDefinition);
       newMethodCallback.accept((T) DexClassAndMethod.create(clazz, methodDefinition));
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 308862a..b5235d2 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -220,7 +220,6 @@
     enableDevirtualization = false;
     enableVerticalClassMerging = false;
     enableEnumUnboxing = false;
-    enableUninstantiatedTypeOptimization = false;
     outline.enabled = false;
     enableEnumValueOptimization = false;
     enableValuePropagation = false;
@@ -329,7 +328,6 @@
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
   public boolean enableRedundantFieldLoadElimination = true;
   public boolean enableValuePropagation = true;
-  public boolean enableUninstantiatedTypeOptimization = true;
   // Currently disabled, see b/146957343.
   public boolean enableUninstantiatedTypeOptimizationForInterfaces = false;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 1ff7079..c5326b3 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -128,10 +128,19 @@
    * were rewritten, otherwise returns defaultValue.
    */
   public static <T> List<T> mapOrElse(List<T> list, Function<T, T> fn, List<T> defaultValue) {
+    return mapOrElse(list, (index, element) -> fn.apply(element), defaultValue);
+  }
+
+  /**
+   * Rewrites the input list based on the given function. Returns the mapped list if any elements
+   * were rewritten, otherwise returns defaultValue.
+   */
+  public static <T> List<T> mapOrElse(
+      List<T> list, IntObjToObjFunction<T, T> fn, List<T> defaultValue) {
     ArrayList<T> result = null;
     for (int i = 0; i < list.size(); i++) {
       T oldElement = list.get(i);
-      T newElement = fn.apply(oldElement);
+      T newElement = fn.apply(i, oldElement);
       if (newElement == oldElement) {
         if (result != null) {
           result.add(oldElement);
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
index df59f20..2d8efe3 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -9,6 +9,10 @@
   CONTINUE,
   BREAK;
 
+  public static TraversalContinuation continueIf(boolean condition) {
+    return condition ? CONTINUE : BREAK;
+  }
+
   public final boolean shouldBreak() {
     return this == BREAK;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
index ef58a2d..3a648fe 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -32,6 +32,10 @@
     return backing.computeIfAbsent(wrap(member), key -> fn.apply(key.get()));
   }
 
+  public boolean containsKey(K member) {
+    return backing.containsKey(wrap(member));
+  }
+
   public void forEach(BiConsumer<K, V> consumer) {
     backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
   }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
index 7569929..6ccce86 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
@@ -17,6 +17,9 @@
 
   public static <T> int run(
       T item1, T item2, NamingLens namingLens, CompareToAccept<T> compareToAccept) {
+    if (item1 == item2) {
+      return 0;
+    }
     CompareToVisitorWithNamingLens state = new CompareToVisitorWithNamingLens(namingLens);
     return compareToAccept.acceptCompareTo(item1, item2, state);
   }
@@ -29,6 +32,9 @@
 
   @Override
   public int visitDexType(DexType type1, DexType type2) {
+    if (type1 == type2) {
+      return 0;
+    }
     return debug(
         namingLens
             .lookupDescriptor(type1)
@@ -37,6 +43,9 @@
 
   @Override
   public int visitDexField(DexField field1, DexField field2) {
+    if (field1 == field2) {
+      return 0;
+    }
     int order = field1.holder.acceptCompareTo(field2.holder, this);
     if (order != 0) {
       return debug(order);
@@ -50,6 +59,9 @@
 
   @Override
   public int visitDexMethod(DexMethod method1, DexMethod method2) {
+    if (method1 == method2) {
+      return 0;
+    }
     int order = method1.holder.acceptCompareTo(method2.holder, this);
     if (order != 0) {
       return debug(order);
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithStringTable.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithStringTable.java
index 78fb663..398e32f 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithStringTable.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithStringTable.java
@@ -19,6 +19,9 @@
 
   @Override
   public int visitDexString(DexString string1, DexString string2) {
+    if (string1 == string2) {
+      return 0;
+    }
     return visitInt(stringTable.applyAsInt(string1), stringTable.applyAsInt(string2));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
index ccb5c9e..f139bc9 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
@@ -14,6 +14,9 @@
 
   public static <T> int run(
       T item1, T item2, RepresentativeMap map, CompareToAccept<T> compareToAccept) {
+    if (item1 == item2) {
+      return 0;
+    }
     CompareToVisitorWithTypeEquivalence state = new CompareToVisitorWithTypeEquivalence(map);
     return compareToAccept.acceptCompareTo(item1, item2, state);
   }
@@ -26,6 +29,9 @@
 
   @Override
   public int visitDexType(DexType type1, DexType type2) {
+    if (type1 == type2) {
+      return 0;
+    }
     DexType repr1 = representatives.getRepresentative(type1);
     DexType repr2 = representatives.getRepresentative(type2);
     return debug(repr1.getDescriptor().acceptCompareTo(repr2.getDescriptor(), this));
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeTable.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeTable.java
index 52a718a..2ec2980 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeTable.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeTable.java
@@ -22,6 +22,9 @@
 
   @Override
   public int visitDexType(DexType type1, DexType type2) {
+    if (type1 == type2) {
+      return 0;
+    }
     return visitInt(typeTable.applyAsInt(type1), typeTable.applyAsInt(type2));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper.java b/src/main/java/com/android/tools/r8/verticalclassmerging/EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper.java
new file mode 100644
index 0000000..dce166b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.verticalclassmerging;
+
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Return;
+
+public class EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper
+    extends InterfaceTypeToClassTypeLensCodeRewriterHelper {
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      InvokeMethod originalInvoke,
+      InvokeMethod rewrittenInvoke,
+      MethodLookupResult lookupResult,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      Return rewrittenReturn,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      FieldPut originalFieldPut,
+      InvokeStatic rewrittenFieldPut,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      FieldPut originalFieldPut,
+      FieldPut rewrittenFieldPut,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void processWorklist() {
+    // Intentionally empty.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java
new file mode 100644
index 0000000..558fdf6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelper.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.verticalclassmerging;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+
+/**
+ * Inserts check-cast instructions after vertical class merging when this is needed for the program
+ * to type check.
+ *
+ * <p>Any class type is assignable to any interface type. If an interface I is merged into its
+ * unique (non-interface) subtype C, then assignments that used to be valid may no longer be valid
+ * due to the stronger type checking imposed by the JVM. Therefore, casts are inserted where
+ * necessary for the program to type check after vertical class merging.
+ *
+ * <p>Example: If the interface I is merged into its unique subclass C, then the invoke-interface
+ * instruction will be rewritten by the {@link com.android.tools.r8.ir.conversion.LensCodeRewriter}
+ * to an invoke-virtual instruction. After this rewriting, the program no longer type checks, and
+ * therefore a cast is inserted before the invoke-virtual instruction: {@code C c = (C) o}.
+ *
+ * <pre>
+ *   Object o = get();
+ *   o.m(); // invoke-interface {o}, void I.m()
+ * </pre>
+ */
+public abstract class InterfaceTypeToClassTypeLensCodeRewriterHelper {
+
+  public static InterfaceTypeToClassTypeLensCodeRewriterHelper create(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCode code,
+      MethodProcessor methodProcessor) {
+    if (methodProcessor.isPrimaryMethodProcessor() && appView.hasVerticallyMergedClasses()) {
+      return new InterfaceTypeToClassTypeLensCodeRewriterHelperImpl(appView, code);
+    }
+    return new EmptyInterfaceTypeToClassTypeLensCodeRewriterHelper();
+  }
+
+  public abstract void insertCastsForOperandsIfNeeded(
+      InvokeMethod originalInvoke,
+      InvokeMethod rewrittenInvoke,
+      MethodLookupResult lookupResult,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator);
+
+  public abstract void insertCastsForOperandsIfNeeded(
+      Return rewrittenReturn,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator);
+
+  public abstract void insertCastsForOperandsIfNeeded(
+      FieldPut originalFieldPut,
+      InvokeStatic rewrittenFieldPut,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator);
+
+  public abstract void insertCastsForOperandsIfNeeded(
+      FieldPut originalFieldPut,
+      FieldPut rewrittenFieldPut,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator);
+
+  public abstract void processWorklist();
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelperImpl.java b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelperImpl.java
new file mode 100644
index 0000000..e97d8c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/InterfaceTypeToClassTypeLensCodeRewriterHelperImpl.java
@@ -0,0 +1,275 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.verticalclassmerging;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.DexType;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+// TODO(b/199561570): Extend this to insert casts for users that are not an instance of
+//  invoke-method (e.g., array-put, instance-put, static-put, return).
+public class InterfaceTypeToClassTypeLensCodeRewriterHelperImpl
+    extends InterfaceTypeToClassTypeLensCodeRewriterHelper {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final IRCode code;
+
+  private final Map<Instruction, Deque<WorklistItem>> worklist = new IdentityHashMap<>();
+
+  public InterfaceTypeToClassTypeLensCodeRewriterHelperImpl(
+      AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code) {
+    this.appView = appView;
+    this.code = code;
+  }
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      InvokeMethod originalInvoke,
+      InvokeMethod rewrittenInvoke,
+      MethodLookupResult lookupResult,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    DexMethod originalInvokedMethod = originalInvoke.getInvokedMethod();
+    DexMethod rewrittenInvokedMethod = rewrittenInvoke.getInvokedMethod();
+    if (lookupResult.getPrototypeChanges().getArgumentInfoCollection().hasRemovedArguments()) {
+      // There is no argument removal before the primary optimization pass.
+      assert false;
+      return;
+    }
+
+    if (originalInvoke.arguments().size()
+        != originalInvokedMethod.getNumberOfArguments(originalInvoke.isInvokeStatic())) {
+      // Wrong number of arguments, this instruction always fails.
+      return;
+    }
+
+    // Intentionally iterate the arguments of the original invoke, since the rewritten invoke could
+    // have extra arguments added.
+    for (int operandIndex = 0; operandIndex < originalInvoke.arguments().size(); operandIndex++) {
+      Value operand = rewrittenInvoke.getArgument(operandIndex);
+      DexType originalType =
+          originalInvokedMethod.getArgumentType(operandIndex, originalInvoke.isInvokeStatic());
+      DexType rewrittenType =
+          rewrittenInvokedMethod.getArgumentType(operandIndex, rewrittenInvoke.isInvokeStatic());
+      if (needsCastForOperand(operand, block, originalType, rewrittenType).isPossiblyTrue()) {
+        addWorklistItem(rewrittenInvoke, operandIndex, originalType, rewrittenType);
+      }
+    }
+  }
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      Return rewrittenReturn,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    assert !rewrittenReturn.isReturnVoid();
+    DexMethod originalMethodSignature =
+        appView.graphLens().getOriginalMethodSignature(code.context().getReference());
+    DexType originalReturnType = originalMethodSignature.getReturnType();
+    DexType rewrittenReturnType = code.context().getReturnType();
+    if (needsCastForOperand(
+            rewrittenReturn.returnValue(), block, originalReturnType, rewrittenReturnType)
+        .isPossiblyTrue()) {
+      addWorklistItem(rewrittenReturn, 0, originalReturnType, rewrittenReturnType);
+    }
+  }
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      FieldPut originalFieldPut,
+      InvokeStatic rewrittenFieldPut,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    DexType originalFieldType = originalFieldPut.getField().getType();
+    int valueIndex = originalFieldPut.getValueIndex();
+    DexType rewrittenFieldType = rewrittenFieldPut.getInvokedMethod().getParameter(valueIndex);
+    Value operand = rewrittenFieldPut.getOperand(valueIndex);
+    if (needsCastForOperand(operand, block, originalFieldType, rewrittenFieldType)
+        .isPossiblyTrue()) {
+      addWorklistItem(rewrittenFieldPut, valueIndex, originalFieldType, rewrittenFieldType);
+    }
+  }
+
+  @Override
+  public void insertCastsForOperandsIfNeeded(
+      FieldPut originalFieldPut,
+      FieldPut rewrittenFieldPut,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    DexType originalFieldType = originalFieldPut.getField().getType();
+    DexType rewrittenFieldType = rewrittenFieldPut.getField().getType();
+    if (needsCastForOperand(rewrittenFieldPut.value(), block, originalFieldType, rewrittenFieldType)
+        .isPossiblyTrue()) {
+      addWorklistItem(
+          rewrittenFieldPut.asFieldInstruction(),
+          rewrittenFieldPut.getValueIndex(),
+          originalFieldType,
+          rewrittenFieldType);
+    }
+  }
+
+  @Override
+  public void processWorklist() {
+    if (worklist.isEmpty()) {
+      return;
+    }
+
+    BasicBlockIterator blockIterator = code.listIterator();
+    boolean isCodeFullyRewrittenWithLens = true;
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator instructionIterator = block.listIterator(code);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        Deque<WorklistItem> worklistItems = worklist.get(instruction);
+        if (worklistItems == null) {
+          continue;
+        }
+        for (WorklistItem worklistItem : worklistItems) {
+          Value operand = instruction.getOperand(worklistItem.operandIndex);
+          DexType originalType = worklistItem.originalType;
+          DexType rewrittenType = worklistItem.rewrittenType;
+          OptionalBool needsCastForOperand =
+              needsCastForOperand(
+                  operand, block, originalType, rewrittenType, isCodeFullyRewrittenWithLens);
+          assert !needsCastForOperand.isUnknown();
+          if (needsCastForOperand.isTrue()) {
+            insertCastForOperand(
+                operand, rewrittenType, instruction, blockIterator, block, instructionIterator);
+          }
+        }
+      }
+    }
+  }
+
+  private void addWorklistItem(
+      Instruction rewrittenInstruction,
+      int operandIndex,
+      DexType originalType,
+      DexType rewrittenType) {
+    worklist
+        .computeIfAbsent(rewrittenInstruction, ignoreKey(ArrayDeque::new))
+        .addLast(new WorklistItem(operandIndex, originalType, rewrittenType));
+  }
+
+  private void insertCastForOperand(
+      Value operand,
+      DexType castType,
+      Instruction rewrittenUser,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      InstructionListIterator instructionIterator) {
+    Instruction previous = instructionIterator.previous();
+    assert previous == rewrittenUser;
+
+    CheckCast checkCast =
+        CheckCast.builder()
+            .setCastType(castType)
+            .setObject(operand)
+            .setFreshOutValue(code, castType.toTypeElement(appView), operand.getLocalInfo())
+            .setPosition(rewrittenUser)
+            .build();
+    if (block.hasCatchHandlers()) {
+      instructionIterator
+          .splitCopyCatchHandlers(code, blockIterator, appView.options())
+          .listIterator(code)
+          .add(checkCast);
+    } else {
+      instructionIterator.add(checkCast);
+    }
+    rewrittenUser.replaceValue(operand, checkCast.outValue());
+
+    Instruction next = instructionIterator.next();
+    assert next == rewrittenUser;
+  }
+
+  private boolean isOperandRewrittenWithLens(
+      Value operand, BasicBlock blockWithUser, boolean isCodeFullyRewrittenWithLens) {
+    if (isCodeFullyRewrittenWithLens) {
+      return true;
+    }
+    if (operand.isPhi()) {
+      return false;
+    }
+    Instruction definition = operand.getDefinition();
+    return definition.isArgument() || operand.getBlock() == blockWithUser;
+  }
+
+  private OptionalBool needsCastForOperand(
+      Value operand, BasicBlock blockWithUser, DexType originalType, DexType rewrittenType) {
+    return needsCastForOperand(operand, blockWithUser, originalType, rewrittenType, false);
+  }
+
+  private OptionalBool needsCastForOperand(
+      Value operand,
+      BasicBlock blockWithUser,
+      DexType originalType,
+      DexType rewrittenType,
+      boolean isCodeFullyRewrittenWithLens) {
+    if (!originalType.isClassType() || !rewrittenType.isClassType()) {
+      return OptionalBool.FALSE;
+    }
+    // The original type should be an interface type.
+    DexProgramClass originalClass = asProgramClassOrNull(appView.definitionFor(originalType));
+    if (originalClass == null || !originalClass.isInterface()) {
+      return OptionalBool.FALSE;
+    }
+    // The rewritten type should be a (non-interface) class type.
+    DexProgramClass rewrittenClass = asProgramClassOrNull(appView.definitionFor(rewrittenType));
+    if (rewrittenClass == null || rewrittenClass.isInterface()) {
+      return OptionalBool.FALSE;
+    }
+    // If the operand has not yet been rewritten with the lens, we delay the type check until
+    // after lens code rewriting.
+    if (!isOperandRewrittenWithLens(operand, blockWithUser, isCodeFullyRewrittenWithLens)) {
+      assert !isCodeFullyRewrittenWithLens;
+      return OptionalBool.UNKNOWN;
+    }
+    // The operand should not be subtype of the rewritten type.
+    TypeElement rewrittenTypeElement = rewrittenType.toTypeElement(appView);
+    return OptionalBool.of(
+        !operand.getType().lessThanOrEqualUpToNullability(rewrittenTypeElement, appView));
+  }
+
+  private static class WorklistItem {
+
+    final int operandIndex;
+    final DexType originalType;
+    final DexType rewrittenType;
+
+    WorklistItem(int operandIndex, DexType originalType, DexType rewrittenType) {
+      this.operandIndex = operandIndex;
+      this.originalType = originalType;
+      this.rewrittenType = rewrittenType;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
index 59f6159..deaaef4 100644
--- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -18,13 +18,26 @@
 import java.util.HashSet;
 import java.util.Set;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class ProguardMapMarkerTest {
-  private static final int EXPECTED_NUMBER_OF_KEYS_DEX = 5;
-  private static final int EXPECTED_NUMBER_OF_KEYS_CF = 4;
+@RunWith(Parameterized.class)
+public class ProguardMapMarkerTest extends TestBase {
+  private static final int EXPECTED_NUMBER_OF_KEYS_DEX = 6;
+  private static final int EXPECTED_NUMBER_OF_KEYS_CF = 5;
   private static final String CLASS_FILE =
       ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ProguardMapMarkerTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
   @Test
   public void proguardMapMarkerTest24() throws CompilationFailedException {
     proguardMapMarkerTestDex(AndroidApiLevel.N);
@@ -128,6 +141,7 @@
     String[] lines = proguardMap.split("\n");
     Set<String> keysFound = new HashSet<>();
     String proguardMapId = null;
+    String proguardMapHash = null;
     for (String line : lines) {
       if (!line.startsWith("#")) {
         continue;
@@ -150,6 +164,8 @@
         assertEquals(VersionProperties.INSTANCE.getSha(), value);
       } else if (key.equals(ProguardMapSupplier.MARKER_KEY_PG_MAP_ID)) {
         proguardMapId = value;
+      } else if (key.equals(ProguardMapSupplier.MARKER_KEY_PG_MAP_HASH)) {
+        proguardMapHash = value;
       } else {
         continue;
       }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 4004a47..dbaa9c9 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -102,6 +102,13 @@
     return addKeepRules(Arrays.asList(rules));
   }
 
+  public T addDontObfuscate(Class<?> clazz) {
+    return addKeepRules(
+        "-keep,allowaccessmodification,allowannotationremoval,allowoptimization,allowshrinking"
+            + " class "
+            + clazz.getTypeName());
+  }
+
   public T addDontOptimize() {
     return addKeepRules("-dontoptimize");
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 14db34c..be6c8f0 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -144,6 +144,7 @@
   private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
 
   public static final String JDK_11_TESTS_DIR = "third_party/openjdk/jdk-11-test/";
+  public static final String JDK_11_TIME_TESTS_DIR = JDK_11_TESTS_DIR + "java/time/";
 
   private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard";
   private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckAllMembersDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckAllMembersDiscardedTest.java
new file mode 100644
index 0000000..dbcd8a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckAllMembersDiscardedTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.checkdiscarded;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.CheckDiscardDiagnostic;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import java.util.List;
+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 CheckAllMembersDiscardedTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean shrinkMethodReferences;
+
+  @Parameter(2)
+  public boolean shrinkTypeReference;
+
+  @Parameters(name = "{0}, shrink method references: {1}, shrink type reference: {2}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void test() throws Exception {
+    AssertUtils.assertFailsCompilationIf(
+        !shrinkMethodReferences || !shrinkTypeReference,
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .addKeepRules(
+                    "-assumenosideeffects class " + Main.class.getTypeName() + " {",
+                    "  static boolean shrinkMethodReferences() return "
+                        + shrinkMethodReferences
+                        + ";",
+                    "  static boolean shrinkTypeReference() return " + shrinkTypeReference + ";",
+                    "}",
+                    // When all members on a class are marked by -checkdiscard, we interpret it as
+                    // if the class should also be fully discarded.
+                    getRuleForSecret("checkdiscard"),
+                    getRuleForSecret("keep,allowshrinking"))
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      if (shrinkMethodReferences && shrinkTypeReference) {
+                        diagnostics.assertNoMessages();
+                      } else {
+                        diagnostics.assertErrorsMatch(diagnosticType(CheckDiscardDiagnostic.class));
+                      }
+                    })
+                .run(parameters.getRuntime(), Main.class)
+                .assertSuccessWithEmptyOutput());
+  }
+
+  private static String getRuleForSecret(String directive) {
+    return StringUtils.joinLines(
+        "-" + directive + " class " + Secret.class.getTypeName() + " {",
+        "  void <init>();",
+        "  static void foo();",
+        "  static void bar();",
+        "}");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      if (!shrinkMethodReferences()) {
+        Secret.foo();
+        Secret.bar();
+      }
+      if (!shrinkTypeReference()) {
+        System.out.println(Secret.class);
+      }
+    }
+
+    static boolean shrinkMethodReferences() {
+      throw new RuntimeException();
+    }
+
+    static boolean shrinkTypeReference() {
+      throw new RuntimeException();
+    }
+  }
+
+  static class Secret {
+
+    static void foo() {
+      System.out.println("Secret Foo");
+    }
+
+    static void bar() {
+      System.out.println("Secret Bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckClassDiscardedEntirelyTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckClassDiscardedEntirelyTest.java
new file mode 100644
index 0000000..97a8f50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckClassDiscardedEntirelyTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.checkdiscarded;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+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.errors.CheckDiscardDiagnostic;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+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 CheckClassDiscardedEntirelyTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean shrink;
+
+  @Parameter(2)
+  public boolean wildcard;
+
+  @Parameters(name = "{0}, shrink: {1}, wildcard: {2}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertFailsCompilationIf(
+        !shrink,
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .addKeepRules(
+                    "-assumenosideeffects class " + Main.class.getTypeName() + " {",
+                    "  static boolean shrink() return " + shrink + ";",
+                    "}",
+                    getRuleForSecret("checkdiscard", wildcard),
+                    // The following call intentionally passes 'true' instead of 'wildcard'. Indeed,
+                    // the -checkdiscard rule for a class implicitly expands to a -checkdiscard rule
+                    // for the class and all of its members. Therefore, the corresponding rule to
+                    // disallow optimizations must apply to all members.
+                    getRuleForSecret("keep,allowshrinking", true))
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      if (shrink) {
+                        diagnostics.assertNoMessages();
+                      } else {
+                        diagnostics.assertAllErrorsMatch(
+                            diagnosticType(CheckDiscardDiagnostic.class));
+                      }
+                    })
+                .inspect(
+                    inspector -> {
+                      // We only get here if the branch in Main.main() is pruned, or the
+                      // -checkdiscard rule should have failed.
+                      assertTrue(shrink);
+                      assertThat(inspector.clazz(Secret.class), isAbsent());
+                    })
+                .run(parameters.getRuntime(), Main.class)
+                .assertSuccessWithEmptyOutput());
+  }
+
+  private static String getRuleForSecret(String directive, boolean wildcard) {
+    return "-" + directive + " class " + Secret.class.getTypeName() + (wildcard ? " { *; }" : "");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      if (!shrink()) {
+        System.out.println(Secret.get());
+      }
+    }
+
+    static boolean shrink() {
+      throw new RuntimeException();
+    }
+  }
+
+  static class Secret {
+
+    static String get() {
+      return "Secret";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index da2e4cb..389d347 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -72,7 +72,7 @@
     options.enableInlining = false;
   }
 
-  private String checkDiscardRule(boolean member, Class annotation) {
+  private String checkDiscardRule(boolean member, Class<?> annotation) {
     if (member) {
       return "-checkdiscard class * { @" + annotation.getName() + " *; }";
     } else {
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckSingleMemberDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckSingleMemberDiscardedTest.java
new file mode 100644
index 0000000..1ca8851
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckSingleMemberDiscardedTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.checkdiscarded;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import java.util.List;
+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 CheckSingleMemberDiscardedTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean shrink;
+
+  @Parameters(name = "{0}, shrink: {1}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  @Test
+  public void test() throws Exception {
+    AssertUtils.assertFailsCompilationIf(
+        !shrink,
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .addKeepRules(
+                    "-assumenosideeffects class " + Main.class.getTypeName() + " {",
+                    "  static boolean shrink() return " + shrink + ";",
+                    "}",
+                    getRuleForSecret("checkdiscard"),
+                    getRuleForSecret("keep,allowshrinking"))
+                .enableInliningAnnotations()
+                .setMinApi(parameters.getApiLevel())
+                .compile()
+                .run(parameters.getRuntime(), Main.class)
+                .assertSuccessWithOutputLines("Public"));
+  }
+
+  private static String getRuleForSecret(String directive) {
+    return StringUtils.joinLines(
+        "-" + directive + " class " + Main.class.getTypeName() + " {",
+        "  static void printSecret();",
+        "}");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      printPublic();
+      if (!shrink()) {
+        printSecret();
+      }
+    }
+
+    @NeverInline
+    static void printPublic() {
+      System.out.println("Public");
+    }
+
+    static void printSecret() {
+      System.out.println("Secret");
+    }
+
+    static boolean shrink() {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckSubclassDiscardedEntirelyTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckSubclassDiscardedEntirelyTest.java
new file mode 100644
index 0000000..0b2fa97
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckSubclassDiscardedEntirelyTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.checkdiscarded;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+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.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.checkdiscarded.CheckClassDiscardedEntirelyTest.Main;
+import com.android.tools.r8.checkdiscarded.CheckClassDiscardedEntirelyTest.Secret;
+import com.android.tools.r8.errors.CheckDiscardDiagnostic;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import java.util.List;
+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 CheckSubclassDiscardedEntirelyTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean shrink;
+
+  @Parameter(2)
+  public boolean wildcard;
+
+  @Parameters(name = "{0}, shrink: {1}, wildcard: {2}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertFailsCompilationIf(
+        !shrink,
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .addKeepRules(
+                    "-assumenosideeffects class " + Main.class.getTypeName() + " {",
+                    "  static boolean shrink() return " + shrink + ";",
+                    "}",
+                    "-checkdiscard class "
+                        + Secret.class.getTypeName()
+                        + (wildcard ? " { *; }" : ""),
+                    // Disable optimizations for items hit by the -checkdiscard rule. Note that
+                    // the evaluation of -checkdiscard rules does not recurse into super classes,
+                    // thus
+                    // to match only the items hit by the -checkdiscard rule we use an -if rule that
+                    // matches the methods on Secret. Specifically, this rule should not keep the
+                    // method Public.printPublicAllowInlining().
+                    "-if class " + Secret.class.getTypeName() + " { *** *(...); }",
+                    "-keep,allowshrinking class " + Secret.class.getTypeName() + " {",
+                    "   <1> <2>(...);",
+                    "}")
+                .addVerticallyMergedClassesInspector(
+                    VerticallyMergedClassesInspector::assertNoClassesMerged)
+                .enableInliningAnnotations()
+                .enableNoVerticalClassMergingAnnotations()
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      if (shrink) {
+                        diagnostics.assertNoMessages();
+                      } else {
+                        diagnostics.assertAllErrorsMatch(
+                            diagnosticType(CheckDiscardDiagnostic.class));
+                      }
+                    })
+                .inspect(
+                    inspector -> {
+                      // We only get here if the branch in Main.main() is pruned, or the
+                      // -checkdiscard
+                      // rule should have failed.
+                      assertTrue(shrink);
+
+                      ClassSubject mainClassSubject = inspector.clazz(Main.class);
+                      assertThat(mainClassSubject, isPresent());
+                      assertTrue(
+                          mainClassSubject
+                              .mainMethod()
+                              .streamInstructions()
+                              .anyMatch(instruction -> instruction.isConstString("Public 2")));
+
+                      ClassSubject publicClassSubject = inspector.clazz(Public.class);
+                      assertThat(publicClassSubject, isPresent());
+                      assertThat(
+                          publicClassSubject.uniqueMethodWithName("printPublic"), isPresent());
+                      assertThat(
+                          publicClassSubject.uniqueMethodWithName("printPublicAllowInlining"),
+                          isAbsent());
+
+                      assertThat(inspector.clazz(Secret.class), isAbsent());
+                    })
+                .run(parameters.getRuntime(), Main.class)
+                .assertSuccessWithOutputLines("Public 1", "Public 2"));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Public.printPublic();
+      Public.printPublicAllowInlining();
+      if (!shrink()) {
+        Secret.printSecret();
+      }
+    }
+
+    static boolean shrink() {
+      throw new RuntimeException();
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class Public {
+
+    @NeverInline
+    static void printPublic() {
+      System.out.println("Public 1");
+    }
+
+    static void printPublicAllowInlining() {
+      System.out.println("Public 2");
+    }
+  }
+
+  static class Secret extends Public {
+
+    static void printSecret() {
+      System.out.println("Secret");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ShrinkFieldsWhileKeepingFieldNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ShrinkFieldsWhileKeepingFieldNameTest.java
new file mode 100644
index 0000000..9358839
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compatproguard/ShrinkFieldsWhileKeepingFieldNameTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.compatproguard;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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 java.util.Objects;
+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 ShrinkFieldsWhileKeepingFieldNameTest extends TestBase {
+
+  private static final String KEEP_FIELD_NAMES_RULE =
+      "-keepclassmembernames,allowoptimization class"
+          + " com.android.tools.r8.compatproguard.ShrinkFieldsWhileKeepingFieldNameTest$Person {"
+          + " <fields>; }";
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("Person[name=John; age=42]", "Person[name=Jane; age=42]");
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ShrinkFieldsWhileKeepingFieldNameTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testShrinkFieldWhileKeepingFieldNameR8() throws Throwable {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Person.class, Main.class)
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .addKeepRules(KEEP_FIELD_NAMES_RULE)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::assertSingleFieldWithOriginalName)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testShrinkFieldWhileKeepingFieldNameR8Compat() throws Throwable {
+    testForR8Compat(parameters.getBackend())
+        .addProgramClasses(Person.class, Main.class)
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .addKeepRules(KEEP_FIELD_NAMES_RULE)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        // TODO(b/200933020): this assert shall pass.
+        // .inspect(this::assertSingleFieldWithOriginalName)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private void assertSingleFieldWithOriginalName(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Person.class);
+    assertEquals(1, clazz.allInstanceFields().size());
+    assertEquals("name", clazz.field("java.lang.String", "name").getFinalName());
+  }
+
+  @NeverClassInline
+  static class Person {
+
+    private final String name;
+    private final int age;
+
+    Person(String name, int age) {
+      this.name = name;
+      this.age = age;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof Person)) {
+        return false;
+      }
+      Person person = (Person) o;
+      return age == person.age && Objects.equals(name, person.name);
+    }
+
+    @Override
+    public String toString() {
+      return "Person[name=" + name + "; age=" + age + "]";
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new Person("John", 42));
+      System.out.println(new Person("Jane", 42));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
index 2d6b833..a0a51c0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeTests.java
@@ -5,19 +5,29 @@
 package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
 
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -47,6 +57,54 @@
     this.parameters = parameters;
   }
 
+  private static final Path JDK_11_TCK_TEST_FILES_DIR =
+      Paths.get(ToolHelper.JDK_11_TIME_TESTS_DIR).resolve("tck");
+  private static final Path JDK_11_TIME_TEST_FILES_DIR =
+      Paths.get(ToolHelper.JDK_11_TIME_TESTS_DIR).resolve("test");
+  private static final String JDK_11_TIME_TEST_EXCLUDE = "TestZoneTextPrinterParser.java";
+  private static Path[] JDK_11_TIME_TEST_COMPILED_FILES;
+
+  private static List<Path> getJdk11TimeTestFiles() throws Exception {
+    List<Path> tckFiles =
+        Files.walk(JDK_11_TCK_TEST_FILES_DIR)
+            .filter(path -> path.toString().endsWith(JAVA_EXTENSION))
+            .filter(path -> !path.toString().endsWith(JDK_11_TIME_TEST_EXCLUDE))
+            .collect(Collectors.toList());
+    List<Path> timeFiles =
+        Files.walk(JDK_11_TIME_TEST_FILES_DIR)
+            .filter(path -> path.toString().endsWith(JAVA_EXTENSION))
+            .filter(path -> !path.toString().endsWith(JDK_11_TIME_TEST_EXCLUDE))
+            .collect(Collectors.toList());
+    ArrayList<Path> files = new ArrayList<>();
+    files.addAll(timeFiles);
+    files.addAll(tckFiles);
+    assert files.size() > 0;
+    return files;
+  }
+
+  @BeforeClass
+  public static void compileJdk11StreamTests() throws Exception {
+    Path tmpDirectory = getStaticTemp().newFolder("time").toPath();
+    List<String> options =
+        Arrays.asList(
+            "--add-reads",
+            "java.base=ALL-UNNAMED",
+            "--patch-module",
+            "java.base=" + JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR);
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
+        .addOptions(options)
+        .addClasspathFiles(
+            ImmutableList.of(
+                Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar"),
+                Paths.get(JDK_TESTS_BUILD_DIR + "jcommander-1.48.jar")))
+        .addSourceFiles(getJdk11TimeTestFiles())
+        .setOutputPath(tmpDirectory)
+        .compile();
+    JDK_11_TIME_TEST_COMPILED_FILES =
+        getAllFilesWithSuffixInDirectory(tmpDirectory, CLASS_EXTENSION);
+    assert JDK_11_TIME_TEST_COMPILED_FILES.length > 0;
+  }
+
   // Following tests are also failing on the Bazel build, they cannot be run easily on
   // Android (difference in time precision, iAndroid printing, etc.).
   private static String[] wontFixFailures =
@@ -145,9 +203,12 @@
         testForD8()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
             .addProgramFiles(getPathsFiles())
-            .addProgramFiles(Paths.get(JDK_TESTS_BUILD_DIR + "jdk11TimeTests.jar"))
+            .addProgramFiles(JDK_11_TIME_TEST_COMPILED_FILES)
             .addProgramFiles(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar"))
             .addProgramFiles(Paths.get(JDK_TESTS_BUILD_DIR + "jcommander-1.48.jar"))
+            .addProgramFiles(
+                Paths.get(
+                    ToolHelper.JAVA_CLASSES_DIR + "examplesTestNGRunner/TestNGMainRunner.class"))
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index 5e7aa9a..56dab1e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -67,23 +67,41 @@
   @Test
   public void testD8Intermediate() throws Exception {
     Assume.assumeTrue(parameters.isDexRuntime());
-    Path path =
-        testForD8(Backend.DEX)
-            .addProgramClassFileData(PROGRAM_DATA)
-            .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .setIntermediate(true)
-            .compile()
-            .writeToZip();
+    Path path = compileIntermediate();
     testForD8()
         .addProgramFiles(path)
         .setMinApi(parameters.getApiLevel())
-        .compile()
+        .setIncludeClassesChecksum(true)
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
+  public void testD8IntermediateNoDesugaringInStep2() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    Path path = compileIntermediate();
+    // In Android Studio they disable desugaring at this point to improve build speed.
+    testForD8()
+        .addProgramFiles(path)
+        .setMinApi(parameters.getApiLevel())
+        .setIncludeClassesChecksum(true)
+        .disableDesugaring()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private Path compileIntermediate() throws Exception {
+    return testForD8(Backend.DEX)
+        .addProgramClassFileData(PROGRAM_DATA)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .setIntermediate(true)
+        .setIncludeClassesChecksum(true)
+        .compile()
+        .writeToZip();
+  }
+
+  @Test
   public void testR8() throws Exception {
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index 938a8f6..38d19da 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -14,7 +14,6 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -33,7 +32,7 @@
 @RunWith(Parameterized.class)
 public class DexSplitterMemberValuePropagationRegression extends SplitterTestBase {
 
-  public static final String EXPECTED = StringUtils.lines("42");
+  public static final String EXPECTED = StringUtils.lines(FeatureEnum.class.getTypeName(), "42");
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
@@ -78,7 +77,8 @@
             ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
             FeatureClass.class,
             ThrowableConsumer.empty(),
-            R8TestBuilder::enableInliningAnnotations);
+            testBuilder ->
+                testBuilder.enableInliningAnnotations().addDontObfuscate(FeatureEnum.class));
     assertEquals(processResult.exitCode, 0);
     assertEquals(processResult.stdout, EXPECTED);
   }
@@ -88,17 +88,26 @@
     @NeverInline
     @Override
     public void run() {
-      System.out.println(getFromFeature());
+      System.out.println(getClassFromFeature().getName());
+      System.out.println(getEnumFromFeature());
     }
 
-    public abstract Enum<?> getFromFeature();
+    public abstract Class<?> getClassFromFeature();
+
+    public abstract Enum<?> getEnumFromFeature();
   }
 
   public static class FeatureClass extends BaseSuperClass {
 
     @NeverInline
     @Override
-    public Enum<?> getFromFeature() {
+    public Class<?> getClassFromFeature() {
+      return FeatureEnum.class;
+    }
+
+    @NeverInline
+    @Override
+    public Enum<?> getEnumFromFeature() {
       return FeatureEnum.A;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index ca64a9f..77da418 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -140,9 +140,7 @@
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
-    // TODO(b/157427150): would be able to remove the call to requireNonNull() if we knew that it
-    //  throws an NullPointerException that does not have a message.
-    test(result, 0, 1);
+    test(result, 0, 0);
   }
 
   static class ObjectsRequireNonNullTestMain {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
new file mode 100644
index 0000000..1411ce8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classmerger.vertical;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+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 ArrayPutToInterfaceWithObjectMergingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, vertical class merging: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(enableVerticalClassMerging);
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .addKeepMainRule(Main.class)
+        // Keep get() to prevent that we optimize it into having static return type A.
+        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addNoVerticalClassMergingAnnotations()
+        .applyIf(
+            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .addVerticallyMergedClassesInspector(
+            inspector -> {
+              if (enableVerticalClassMerging) {
+                inspector.assertMergedIntoSubtype(I.class);
+              } else {
+                inspector.assertNoClassesMerged();
+              }
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  private static byte[] getTransformedMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("get")) {
+                visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .setReturnType(MethodPredicate.onName("get"), Object.class.getTypeName())
+        .transform();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I[] is = new I[1];
+      // Transformed from `I get()` to `Object get()`.
+      is[0] = get();
+      print(is);
+    }
+
+    // @Keep
+    static /*Object*/ I get() {
+      return new A();
+    }
+
+    @NeverInline
+    static void print(I[] is) {
+      System.out.println(is[0]);
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {}
+
+  static class A implements I {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
new file mode 100644
index 0000000..980eebb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/InstancePutToInterfaceWithObjectMergingTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classmerger.vertical;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+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 InstancePutToInterfaceWithObjectMergingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, vertical class merging: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(enableVerticalClassMerging);
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .addKeepMainRule(Main.class)
+        // Keep get() to prevent that we optimize it into having static return type A.
+        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addNoVerticalClassMergingAnnotations()
+        .applyIf(
+            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .addVerticallyMergedClassesInspector(
+            inspector -> {
+              if (enableVerticalClassMerging) {
+                inspector.assertMergedIntoSubtype(I.class);
+              } else {
+                inspector.assertNoClassesMerged();
+              }
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  private static byte[] getTransformedMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("get")) {
+                visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .setReturnType(MethodPredicate.onName("get"), Object.class.getTypeName())
+        .transform();
+  }
+
+  static class Main {
+
+    I f;
+
+    public static void main(String[] args) {
+      Main main = new Main();
+      // Transformed from `I get()` to `Object get()`.
+      main.f = get();
+      print(main);
+    }
+
+    // @Keep
+    static /*Object*/ I get() {
+      return new A();
+    }
+
+    @NeverInline
+    static void print(Main main) {
+      System.out.println(main.f);
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {}
+
+  static class A implements I {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
new file mode 100644
index 0000000..48b234b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ReturnObjectAsInterfaceMergingTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classmerger.vertical;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+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 ReturnObjectAsInterfaceMergingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, vertical class merging: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(enableVerticalClassMerging);
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .addKeepMainRule(Main.class)
+        // Keep get() to prevent that we optimize it into having static return type A.
+        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addNoVerticalClassMergingAnnotations()
+        .applyIf(
+            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .addVerticallyMergedClassesInspector(
+            inspector -> {
+              if (enableVerticalClassMerging) {
+                inspector.assertMergedIntoSubtype(I.class);
+              } else {
+                inspector.assertNoClassesMerged();
+              }
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  private static byte[] getTransformedMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "test",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("get")) {
+                visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .setReturnType(MethodPredicate.onName("get"), Object.class.getTypeName())
+        .transform();
+  }
+
+  static class Main {
+
+    static I f;
+
+    public static void main(String[] args) {
+      System.out.println(test());
+    }
+
+    @NeverInline
+    static I test() {
+      // Transformed from `I get()` to `Object get()`.
+      return get();
+    }
+
+    // @Keep
+    static /*Object*/ I get() {
+      return new A();
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {}
+
+  static class A implements I {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
new file mode 100644
index 0000000..bb948cbe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/StaticPutToInterfaceWithObjectMergingTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classmerger.vertical;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+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 StaticPutToInterfaceWithObjectMergingTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, vertical class merging: {0}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(enableVerticalClassMerging);
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .addKeepMainRule(Main.class)
+        // Keep get() to prevent that we optimize it into having static return type A.
+        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addNoVerticalClassMergingAnnotations()
+        .applyIf(
+            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .addVerticallyMergedClassesInspector(
+            inspector -> {
+              if (enableVerticalClassMerging) {
+                inspector.assertMergedIntoSubtype(I.class);
+              } else {
+                inspector.assertNoClassesMerged();
+              }
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  private static byte[] getTransformedMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("get")) {
+                visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .setReturnType(MethodPredicate.onName("get"), Object.class.getTypeName())
+        .transform();
+  }
+
+  static class Main {
+
+    static I f;
+
+    public static void main(String[] args) {
+      // Transformed from `I get()` to `Object get()`.
+      f = get();
+      print();
+    }
+
+    // @Keep
+    static /*Object*/ I get() {
+      return new A();
+    }
+
+    @NeverInline
+    static void print() {
+      System.out.println(f);
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {}
+
+  static class A implements I {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
new file mode 100644
index 0000000..8573c7b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InterfaceInvokeWithObjectReceiverInliningTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+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 InterfaceInvokeWithObjectReceiverInliningTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableInlining;
+
+  @Parameter(1)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(2)
+  public TestParameters parameters;
+
+  @Parameters(name = "{2}, inlining: {0}, vertical class merging: {1}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(enableInlining);
+    assumeFalse(enableVerticalClassMerging);
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .addKeepMainRule(Main.class)
+        // Keep get() to prevent that we optimize it into having static return type A.
+        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+        .addInliningAnnotations()
+        .addNoVerticalClassMergingAnnotations()
+        .applyIf(!enableInlining, R8TestBuilder::enableInliningAnnotations)
+        .applyIf(
+            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .addVerticallyMergedClassesInspector(
+            inspector -> {
+              if (enableVerticalClassMerging) {
+                inspector.assertMergedIntoSubtype(I.class);
+              } else {
+                inspector.assertNoClassesMerged();
+              }
+            })
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  private static byte[] getTransformedMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("get")) {
+                visitor.visitMethodInsn(opcode, owner, name, "(I)Ljava/lang/Object;", isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .setReturnType(MethodPredicate.onName("get"), Object.class.getTypeName())
+        .transform();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      // Transformed from `I get(int)` to `Object get(int)`.
+      get(args.length).m();
+    }
+
+    // @Keep
+    static /*Object*/ I get(int f) {
+      return new A(f);
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface I {
+
+    void m();
+  }
+
+  static class A implements I {
+
+    int f;
+
+    A(int f) {
+      this.f = f;
+    }
+
+    @NeverInline
+    @Override
+    public void m() {
+      System.out.println(f);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
new file mode 100644
index 0000000..0c9a135
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/StaticInvokeWithMultipleObjectsForInterfaceTypesTest.java
@@ -0,0 +1,160 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+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 StaticInvokeWithMultipleObjectsForInterfaceTypesTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableInlining;
+
+  @Parameter(1)
+  public boolean enableVerticalClassMerging;
+
+  @Parameter(2)
+  public TestParameters parameters;
+
+  @Parameters(name = "{2}, inlining: {0}, vertical class merging: {1}")
+  public static List<Object[]> parameters() {
+    return buildParameters(
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(enableInlining);
+    assumeFalse(enableVerticalClassMerging);
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, J.class, A.class, B.class)
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0", "0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, A.class, B.class)
+        .addProgramClassFileData(getTransformedMain())
+        .addKeepMainRule(Main.class)
+        // Keep getA() and getB() to prevent that we optimize it into having static return type A/B.
+        .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get?(...); }")
+        .addInliningAnnotations()
+        .addNoVerticalClassMergingAnnotations()
+        .applyIf(!enableInlining, R8TestBuilder::enableInliningAnnotations)
+        .applyIf(
+            !enableVerticalClassMerging, R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0", "0");
+  }
+
+  private static byte[] getTransformedMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.startsWith("get")) {
+                visitor.visitMethodInsn(opcode, owner, name, "(I)Ljava/lang/Object;", isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .setReturnType(MethodPredicate.onName("getA"), Object.class.getTypeName())
+        .setReturnType(MethodPredicate.onName("getB"), Object.class.getTypeName())
+        .transform();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      // Transformed from `I getA(int)` to `Object getA(int)` and `J getB(int)` to
+      // `Object getB(int)`.
+      test(getA(args.length), getB(args.length));
+    }
+
+    // @Keep
+    static /*Object*/ I getA(int f) {
+      return new A(f);
+    }
+
+    // @Keep
+    static /*Object*/ J getB(int f) {
+      return new B(f);
+    }
+
+    @NeverInline
+    static void test(I i, J j) {
+      i.m();
+      j.m();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  interface I {
+
+    void m();
+  }
+
+  @NoHorizontalClassMerging
+  static class A implements I {
+
+    int f;
+
+    A(int f) {
+      this.f = f;
+    }
+
+    @Override
+    public void m() {
+      System.out.println(f);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  interface J {
+
+    void m();
+  }
+
+  @NoHorizontalClassMerging
+  static class B implements J {
+
+    int f;
+
+    B(int f) {
+      this.f = f;
+    }
+
+    @Override
+    public void m() {
+      System.out.println(f);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index c3f71e3..615aa20 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -204,9 +204,9 @@
 
     assertEquals(
         Lists.newArrayList(
-            "STATIC: SimpleWithThrowingGetter SimpleWithThrowingGetter.getInstance()",
-            "STATIC: SimpleWithThrowingGetter SimpleWithThrowingGetter.getInstance()",
             "STATIC: String TrivialTestClass.next()",
+            "STATIC: void SimpleWithThrowingGetter.getInstance()",
+            "STATIC: void SimpleWithThrowingGetter.getInstance()",
             "SimpleWithThrowingGetter SimpleWithThrowingGetter.INSTANCE",
             "VIRTUAL: String SimpleWithThrowingGetter.bar(String)",
             "VIRTUAL: String SimpleWithThrowingGetter.foo()"),
@@ -399,7 +399,6 @@
 
   private void configure(InternalOptions options) {
     options.enableClassInlining = false;
-    options.enableUninstantiatedTypeOptimization = false;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
index fe09d69..6750562 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/InvokeStaticWithNullOutvalueTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -45,6 +46,7 @@
         .addInnerClasses(InvokeStaticWithNullOutvalueTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -84,6 +86,7 @@
     @NoHorizontalClassMerging
     static class Companion {
       @NeverInline
+      @NeverPropagateValue
       private static Object boo() {
         System.out.println("Companion#boo");
         return null;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 3861a06..e3fe71a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -89,7 +89,7 @@
     int expectedCount = isR8 ? 4 : (isRelease ? 5 : 7);
     assertEquals(expectedCount, countCall(mainMethod, "String", "valueOf"));
     // Due to the different behavior regarding constant canonicalization.
-    expectedCount = isR8 ? (parameters.isCfRuntime() ? 3 : 1) : 1;
+    expectedCount = isR8 ? (parameters.isCfRuntime() ? 4 : 1) : 1;
     assertEquals(expectedCount, countConstNullNumber(mainMethod));
     expectedCount = isR8 ? 1 : (isRelease ? 1 : 0);
     assertEquals(expectedCount, countNullStringNumber(mainMethod));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
index 209f9f9..91a886d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.google.common.base.Predicates.alwaysTrue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -12,10 +13,12 @@
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+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.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -25,45 +28,53 @@
 @RunWith(Parameterized.class)
 public class InterfaceMethodTest extends TestBase {
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public InterfaceMethodTest(Backend backend) {
-    this.backend = backend;
+  public InterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
     String expectedOutput = StringUtils.lines("In A.m()", "In B.m()");
 
-    if (backend == Backend.CF) {
-      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addTestClasspath()
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput);
     }
 
     CodeInspector inspector =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addInnerClasses(InterfaceMethodTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .enableNoVerticalClassMergingAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
-            .run(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
     ClassSubject interfaceSubject = inspector.clazz(I.class);
     assertThat(interfaceSubject, isPresent());
-    assertThat(interfaceSubject.method(Uninstantiated.class.getTypeName(), "m"), isPresent());
+
+    MethodSubject interfaceMethodSubject = interfaceSubject.uniqueMethodThatMatches(alwaysTrue());
+    assertThat(interfaceMethodSubject, isPresent());
 
     for (Class<?> clazz : ImmutableList.of(A.class, B.class)) {
       ClassSubject classSubject = inspector.clazz(clazz);
       assertThat(classSubject, isPresent());
-      assertThat(classSubject.method(Uninstantiated.class.getTypeName(), "m"), isPresent());
+      assertThat(
+          classSubject.method(interfaceMethodSubject.getProgramMethod().getMethodReference()),
+          isPresent());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithReceiverOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithReceiverOptimizationTest.java
index 06aae25..eca11f0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithReceiverOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithReceiverOptimizationTest.java
@@ -64,10 +64,8 @@
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
             .addOptionsModification(
-                options -> {
-                  options.enableUninstantiatedTypeOptimization = enableArgumentPropagation;
-                  options.callSiteOptimizationOptions().setEnabled(enableArgumentPropagation);
-                })
+                options ->
+                    options.callSiteOptimizationOptions().setEnabled(enableArgumentPropagation))
             // TODO(b/120764902): The calls to getOriginalName() below does not work in presence of
             //  argument removal.
             .noMinification()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index f07d6b6..44d3e31 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -43,7 +44,10 @@
     String expectedOutput = StringUtils.lines("In A.m()", "In A.m()");
 
     if (parameters.isCfRuntime()) {
-      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+      testForJvm()
+          .addTestClasspath()
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput);
     }
 
     CodeInspector inspector =
@@ -66,11 +70,19 @@
 
     ClassSubject interfaceSubject = inspector.clazz(I.class);
     assertThat(interfaceSubject, isPresent());
-    assertThat(interfaceSubject.method(Uninstantiated.class.getTypeName(), "m"), isPresent());
+
+    MethodSubject interfaceMethodSubject =
+        interfaceSubject.uniqueMethodThatMatches(
+            method -> method.getProgramMethod().getReturnType().isVoidType());
+    assertThat(interfaceMethodSubject, isPresent());
 
     ClassSubject classSubject = inspector.clazz(A.class);
     assertThat(classSubject, isPresent());
-    assertThat(classSubject.method(Uninstantiated.class.getTypeName(), "m"), isPresent());
+    assertThat(
+        classSubject.uniqueMethodThatMatches(
+            method ->
+                method.getProgramMethod().getReference().match(interfaceMethodSubject.getMethod())),
+        isPresent());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
index 5e5345a..472ec24 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -76,15 +77,9 @@
 
     ClassSubject subSubFactoryClassSubject = inspector.clazz(SubSubFactory.class);
     assertThat(subSubFactoryClassSubject.method("void", "createVirtual"), isPresent());
-    assertThat(
-        subSubFactoryClassSubject.method(SubUninstantiated.class.getTypeName(), "createVirtual"),
-        isPresent());
-
-    // TODO(b/110806787): Uninstantiated is kept because SubUninstantiated inherits from it.
-    // We should consider rewriting SubUninstantiated such that it no longer inherits from
-    // Uninstantiated.
-    assertThat(inspector.clazz(Uninstantiated.class), isPresent());
-    assertThat(inspector.clazz(SubUninstantiated.class), isPresent());
+    assertThat(subSubFactoryClassSubject.method("void", "createVirtual$1"), isPresent());
+    assertThat(inspector.clazz(Uninstantiated.class), isAbsent());
+    assertThat(inspector.clazz(SubUninstantiated.class), isAbsent());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
index 612a0b5..e990830 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -32,7 +33,8 @@
 
   @Parameters(name = "{1}, minification: {0}")
   public static List<Object[]> params() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public CollisionWithLibraryMethodsTest(boolean minification, TestParameters parameters) {
@@ -46,10 +48,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CollisionWithLibraryMethodsTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableMemberValuePropagationAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .minification(minification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verify)
         .run(parameters.getRuntime(), TestClass.class)
@@ -83,6 +86,7 @@
   static class Greeting {
 
     @NeverInline
+    @NeverPropagateValue
     public String toString(Object unused) {
       System.out.print("Hello ");
       return "world!";
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 105008c..f3db67c 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -203,7 +204,7 @@
         IRCode inlinee,
         ListIterator<BasicBlock> blockIterator,
         Set<BasicBlock> blocksToRemove,
-        DexType downcast) {
+        DexProgramClass downcast) {
       throw new Unimplemented();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/naming/MappingFileHashTest.java b/src/test/java/com/android/tools/r8/naming/MappingFileHashTest.java
new file mode 100644
index 0000000..23f8605
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MappingFileHashTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+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.naming.ProguardMapSupplier.ProguardMapChecker;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker.VerifyMappingFileHashResult;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MappingFileHashTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public MappingFileHashTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testUnixLineEnd() throws Exception {
+    String newline = "\n";
+    // Hash as given by sha256 command on the content using unix-style newlines.
+    String hash = "539a6b5614c65c7473a995507163a4559cea3229d3cada164a2ca4935bbbd597";
+    verifyHash(newline, hash);
+  }
+
+  @Test
+  public void testWindowsLineEnd() throws Exception {
+    // R8 will only use \n in mapping files, but if a file is written using dos-style newlines,
+    // those newlines will be part of the content hashing. This is a regression test that the file
+    // will compute the right hash in such cases.
+    String newline = "\r\n";
+    // Hash as given by sha256 command on the content using dos-style newlines.
+    String hash = "6ef47f7b9b6d5013628073ebdadad27dd2e7e57ef911e9d6cbee0f92c434e48a";
+    verifyHash(newline, hash);
+  }
+
+  private void verifyHash(String newline, String hash) throws Exception {
+    // Some header info. This should never affect the hash.
+    String header =
+        String.join(
+            newline,
+            "# compiler: R8",
+            "", // Empty header line.
+            "# junk header info: " + System.nanoTime(),
+            "# pg_map_hash: SHA-256 " + hash);
+    // Actually hashed content (modulo newlines).
+    String hashed =
+        String.join(
+            newline,
+            "triviæl.Triviål -> a.œ:",
+            "    void main(java.lang.String[]) -> a",
+            "" // Always end with newline.
+            );
+    String content = String.join(newline, header, hashed);
+    VerifyMappingFileHashResult result = ProguardMapChecker.validateProguardMapHash(content);
+    assertTrue(result.isOk());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index c4c9fdf..8771ff4 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.errors.Unreachable;
@@ -41,7 +42,8 @@
           Super.class,
           TestClass.class,
           OtherPackageSuper.class,
-          OtherPackageTestClass.class);
+          OtherPackageTestClass.class,
+          NeverPropagateValue.class);
 
   private final Shrinker shrinker;
   private final String repackagePrefix;
@@ -212,7 +214,7 @@
   @Test
   public void test_keepPublic() throws Exception {
     Assume.assumeFalse(shrinker.generatesDex() && vmVersionIgnored());
-    Class mainClass = TestMain.class;
+    Class<?> mainClass = TestMain.class;
     String keep = !minify ? "-keep" : "-keep,allowobfuscation";
     Iterable<String> config = ImmutableList.of(
         "-printmapping",
@@ -238,6 +240,9 @@
                   "-assumemayhavesideeffects class " + TestClass.class.getCanonicalName() + " {",
                   "  * staticMethod();",
                   "}",
+                  "-neverpropagatevalue class * {",
+                  "  @com.android.tools.r8.NeverPropagateValue <methods>;",
+                  "}",
                   "-neverinline class " + TestClass.class.getCanonicalName() + " {",
                   "  * staticMethod();",
                   "}"));
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
index f13700c..451a817 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming.b72391662;
 
+import com.android.tools.r8.NeverPropagateValue;
 import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
@@ -22,6 +23,7 @@
     System.out.println("This should be discarded unless there is a keep rule.");
   }
 
+  @NeverPropagateValue
   static String staticMethod() {
     return "1";
   }
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java
index 6660196..298c597 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java
@@ -3,19 +3,24 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming.overloadaggressively;
 
+import com.android.tools.r8.NeverPropagateValue;
+
 public class B {
   volatile int f1 = 8;
   volatile Object f2 = "d8";
   volatile String f3 = "r8";
 
+  @NeverPropagateValue
   public int getF1() {
     return f1;
   }
 
+  @NeverPropagateValue
   public Object getF2() {
     return f2;
   }
 
+  @NeverPropagateValue
   public String getF3() {
     return f3;
   }
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index 5c48866..e691d86 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+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.google.common.collect.ImmutableList;
@@ -41,7 +42,7 @@
     this.backend = backend;
   }
 
-  private AndroidApp runR8(AndroidApp app, Class main, Path out, boolean overloadaggressively)
+  private AndroidApp runR8(AndroidApp app, Class<?> main, Path out, boolean overloadaggressively)
       throws Exception {
     R8Command command =
         ToolHelper.addProguardConfigurationConsumer(
@@ -172,21 +173,36 @@
   }
 
   private void methodResolution(boolean overloadaggressively) throws Exception {
-    byte[][] classes = {
-        ToolHelper.getClassAsBytes(MethodResolution.class),
-        ToolHelper.getClassAsBytes(B.class)
-    };
-    AndroidApp originalApp = buildAndroidApp(classes);
-    Path out = temp.getRoot().toPath();
-    AndroidApp processedApp = runR8(originalApp, MethodResolution.class, out, overloadaggressively);
+    String expected = StringUtils.lines("diff: 0", "d8 v.s. d8", "r8 v.s. r8");
+    String expectedOverloadAggressively = StringUtils.lines("diff: 0", "d8 v.s. 8", "r8 v.s. 8");
 
-    CodeInspector codeInspector = new CodeInspector(processedApp);
+    if (backend.isCf()) {
+      testForJvm().addTestClasspath().run(MethodResolution.class).assertSuccessWithOutput(expected);
+    }
+
+    testForR8Compat(backend)
+        .addProgramClasses(MethodResolution.class, B.class)
+        .addKeepMainRule(MethodResolution.class)
+        .addOptionsModification(options -> options.enableInlining = false)
+        .applyIf(overloadaggressively, builder -> builder.addKeepRules("-overloadaggressively"))
+        .enableMemberValuePropagationAnnotations()
+        .compile()
+        .inspect(inspector -> inspect(inspector, overloadaggressively))
+        .run(MethodResolution.class)
+        .applyIf(
+            overloadaggressively,
+            runResult -> runResult.assertSuccessWithOutput(expectedOverloadAggressively),
+            runResult -> runResult.assertSuccessWithOutput(expected));
+  }
+
+  private void inspect(CodeInspector codeInspector, boolean overloadaggressively) {
     ClassSubject b = codeInspector.clazz(B.class.getCanonicalName());
     DexEncodedMethod m1 =
         b.method("int", "getF1", ImmutableList.of()).getMethod();
     assertNotNull(m1);
     DexEncodedMethod m2 =
         b.method("java.lang.Object", "getF2", ImmutableList.of()).getMethod();
+    assertNotNull(m2);
     // TODO(b/72858955): due to the potential reflective access, they should have different names.
     assertEquals(overloadaggressively, m1.getReference().name == m2.getReference().name);
     DexEncodedMethod m3 =
@@ -196,21 +212,6 @@
     assertEquals(overloadaggressively, m1.getReference().name == m3.getReference().name);
     // TODO(b/72858955): ditto
     assertEquals(overloadaggressively, m2.getReference().name == m3.getReference().name);
-
-    String main = MethodResolution.class.getCanonicalName();
-    ProcessResult javaOutput = runOnJavaRaw(main, classes);
-    assertEquals(0, javaOutput.exitCode);
-    ProcessResult output = runRaw(processedApp, main);
-    // TODO(b/72858955): R8 should avoid method resolution conflict even w/ -overloadaggressively.
-    if (overloadaggressively) {
-      assertEquals(0, output.exitCode);
-      assertNotEquals(javaOutput.stdout.trim(), output.stdout.trim());
-    } else {
-      assertEquals(0, output.exitCode);
-      assertEquals(javaOutput.stdout.trim(), output.stdout.trim());
-      // ART may dump its own debugging info through stderr.
-      // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
-    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java
new file mode 100644
index 0000000..232fe30
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import java.util.List;
+import org.junit.Before;
+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 RetraceInlineeWithNullCheck extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Caller.class)
+            .assertFailure()
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Caller.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), Caller.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        // TODO(b/197936862): The two should be the same
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              assertThat(stackTrace, not(isSame(expectedStackTrace)));
+            });
+  }
+
+  static class Foo {
+    @NeverInline
+    Object notInlinable() {
+      System.out.println("Hello, world!");
+      throw new RuntimeException("Foo");
+    }
+
+    Object inlinable() {
+      return notInlinable();
+    }
+  }
+
+  @NeverClassInline
+  static class Caller {
+    @NeverInline
+    static void caller(Foo f) {
+      f.inlinable();
+    }
+
+    public static void main(String[] args) {
+      caller(System.currentTimeMillis() < 0 ? new Foo() : null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ConstantReturnAfterArgumentPropagationTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ConstantReturnAfterArgumentPropagationTest.java
new file mode 100644
index 0000000..ba07988
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ConstantReturnAfterArgumentPropagationTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstantReturnAfterArgumentPropagationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        // TODO(b/173398086): uniqueMethodWithName() does not work with argument removal.
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              MethodSubject identityMethodSubject =
+                  mainClassSubject.uniqueMethodWithName("identity");
+              assertThat(identityMethodSubject, isPresent());
+              assertTrue(identityMethodSubject.getProgramMethod().getParameters().isEmpty());
+              assertTrue(identityMethodSubject.getProgramMethod().getReturnType().isVoidType());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("identity(42) = 42");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(identity("42"));
+    }
+
+    @NeverInline
+    static Object identity(Object constant) {
+      System.out.print("identity(" + constant + ") = ");
+      return constant;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 9872e09..4bba10f 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -206,6 +207,44 @@
     assertFalse(processResult.stdout.startsWith(WAITING_MESSAGE));
   }
 
+  @Test
+  public void testNoMappingFileHash() throws IOException {
+    Path mappingFile = folder.newFile("mapping.txt").toPath();
+    Files.write(mappingFile, ("# other header\n" + "foo.bar -> a.a\n").getBytes());
+    ProcessResult result =
+        runRetraceCommandLine(
+            null, ImmutableList.of(mappingFile.toString(), "--verify-mapping-file-hash"));
+    assertEquals(result.toString(), 0, result.exitCode);
+    assertEquals("", result.stdout);
+    assertThat(result.stderr, containsString("Failure to find map hash"));
+  }
+
+  @Test
+  public void testValidMappingFileHash() throws IOException {
+    Path mappingFile = folder.newFile("mapping.txt").toPath();
+    Files.write(
+        mappingFile,
+        ("# pg_map_hash: SHA-256 aaf7c0230ea6fa768189170543c86ec202c6180d1e0a37b620e5c1fce1bd3ae7\n"
+                + "foo.bar -> a.a\n")
+            .getBytes());
+    ProcessResult result =
+        runRetraceCommandLine(
+            null, ImmutableList.of(mappingFile.toString(), "--verify-mapping-file-hash"));
+    assertEquals(result.toString(), 0, result.exitCode);
+    assertEquals("", result.stdout);
+    assertEquals("", result.stderr);
+  }
+
+  @Test
+  public void testInvalidMappingFileHash() throws IOException {
+    Path mappingFile = folder.newFile("mapping.txt").toPath();
+    Files.write(mappingFile, ("# pg_map_hash: SHA-256 abcd1234\n" + "foo.bar -> a.a\n").getBytes());
+    runAbortTest(
+        containsString("Mismatching map hash"),
+        mappingFile.toString(),
+        "--verify-mapping-file-hash");
+  }
+
   private final String nonMappableStackTrace =
       StringUtils.lines(
           "com.android.r8.R8Exception: Problem when compiling program",
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index d1a074b..34e4ca8 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.retrace.stacktraces.AutoStackTrace;
 import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ColonInFileNameStackTrace;
+import com.android.tools.r8.retrace.stacktraces.DifferentLineNumberSpanStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
@@ -39,6 +40,7 @@
 import com.android.tools.r8.retrace.stacktraces.MemberFieldOverlapStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleDotsInFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.MultipleLinesNoLineNumberStackTrace;
+import com.android.tools.r8.retrace.stacktraces.MultipleOriginalLinesNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NoObfuscatedLineNumberWithOverrideTest;
 import com.android.tools.r8.retrace.stacktraces.NoObfuscationRangeMappingWithStackTrace;
@@ -315,6 +317,16 @@
     runExperimentalRetraceTest(new NpeInlineRetraceStackTrace());
   }
 
+  @Test
+  public void testMultipleOriginalLinesNoLineNumberStackTrace() throws Exception {
+    runRetraceTest(new MultipleOriginalLinesNoLineNumberStackTrace());
+  }
+
+  @Test
+  public void testDifferentLineNumberSpanStackTrace() throws Exception {
+    runRetraceTest(new DifferentLineNumberSpanStackTrace());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
new file mode 100644
index 0000000..d54bafa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiRewriteFrameInlineNpeResidualTest extends RetraceApiTestBase {
+
+  public RetraceApiRewriteFrameInlineNpeResidualTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference originalException = Reference.classFromTypeName("foo.bar.baz");
+    private final ClassReference renamedException = Reference.classFromTypeName("c");
+
+    private final ClassReference originalSomeClass = Reference.classFromTypeName("some.Class");
+    private final ClassReference renamedSomeClass = Reference.classFromTypeName("a");
+
+    private final ClassReference originalSomeOtherClass =
+        Reference.classFromTypeName("some.other.Class");
+    private final ClassReference renamedSomeOtherClass = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }\n"
+            + originalException.getTypeName()
+            + " -> "
+            + renamedException.getTypeName()
+            + ":\n"
+            + originalSomeClass.getTypeName()
+            + " -> "
+            + renamedSomeClass.getTypeName()
+            + ":\n"
+            + "  4:4:void other.Class.inlinee():23:23 -> a\n"
+            + "  4:4:void caller(other.Class):7 -> a\n"
+            + "  # { id: 'com.android.tools.r8.rewriteFrame', "
+            + "      conditions: ['throws(Lc;)'], "
+            + "      actions: ['removeInnerFrames(1)'] "
+            + "    }\n"
+            + originalSomeOtherClass.getTypeName()
+            + " -> "
+            + renamedSomeOtherClass.getTypeName()
+            + ":\n"
+            + "  4:4:void other.Class.inlinee2():23:23 -> a\n"
+            + "  4:4:void caller(other.Class):7 -> a\n"
+            + "  # { id: 'com.android.tools.r8.rewriteFrame', "
+            + "      conditions: ['throws(Lfoo/bar/baz;)'], "
+            + "      actions: ['removeInnerFrames(1)'] "
+            + "    }";
+
+    @Test
+    public void testUsingObfuscatedName() {
+      Retracer retracer =
+          Retracer.createExperimental(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      List<RetraceThrownExceptionElement> npeRetraced =
+          retracer
+              .retraceThrownException(renamedException, RetraceStackTraceContext.empty())
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, npeRetraced.size());
+      assertEquals(originalException, npeRetraced.get(0).getRetracedClass().getClassReference());
+
+      checkRewrittenFrame(
+          retracer, npeRetraced.get(0).getContext(), renamedSomeClass.getTypeName(), "a", true);
+      checkRewrittenFrame(
+          retracer,
+          npeRetraced.get(0).getContext(),
+          renamedSomeOtherClass.getTypeName(),
+          "a",
+          false);
+    }
+
+    private void checkRewrittenFrame(
+        Retracer retracer,
+        RetraceStackTraceContext throwingContext,
+        String typeName,
+        String methodName,
+        boolean shouldRemove) {
+      List<RetraceFrameElement> retraceFrameElements =
+          retracer.retraceClass(Reference.classFromTypeName(typeName)).stream()
+              .flatMap(
+                  element ->
+                      element.lookupFrame(throwingContext, OptionalInt.of(4), methodName).stream())
+              .collect(Collectors.toList());
+      assertEquals(1, retraceFrameElements.size());
+
+      RetraceFrameElement retraceFrameElement = retraceFrameElements.get(0);
+      // Check that rewriting the frames will remove the top 1 frames if the condition is active.
+      Map<Integer, RetracedMethodReference> results = new LinkedHashMap<>();
+      retraceFrameElement.forEachRewritten(
+          throwingContext,
+          frame -> {
+            RetracedMethodReference existingValue =
+                results.put(frame.getIndex(), frame.getMethodReference());
+            assertNull(existingValue);
+          });
+      if (shouldRemove) {
+        assertEquals(1, results.size());
+        assertEquals(7, results.get(0).getOriginalPositionOrDefault(4));
+        assertEquals(results.get(0).getMethodName(), "caller");
+      } else {
+        assertEquals(2, results.size());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
index 41c56cc..9759f21 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
@@ -12,16 +12,16 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.RetraceClassElement;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
 import com.android.tools.r8.retrace.RetracedMethodReference;
 import com.android.tools.r8.retrace.Retracer;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
+import java.util.OptionalInt;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -62,28 +62,34 @@
           Retracer.createExperimental(
               ProguardMapProducer.fromString(mapping), testDiagnosticsHandler);
 
-      List<RetraceClassElement> npeRetraced =
-          retracer.retraceClass(Reference.classFromDescriptor(npeDescriptor)).stream()
+      List<RetraceThrownExceptionElement> npeRetraced =
+          retracer
+              .retraceThrownException(
+                  Reference.classFromDescriptor(npeDescriptor), RetraceStackTraceContext.empty())
+              .stream()
               .collect(Collectors.toList());
       assertEquals(1, npeRetraced.size());
 
-      RetraceStackTraceContext context = npeRetraced.get(0).getContextWhereClassWasThrown();
+      RetraceStackTraceContext throwingContext = npeRetraced.get(0).getContext();
 
       List<RetraceFrameElement> retraceFrameElements =
           retracer.retraceClass(Reference.classFromTypeName("a")).stream()
-              .flatMap(element -> element.lookupFrame(Optional.of(4), "a").stream())
+              .flatMap(
+                  element -> element.lookupFrame(throwingContext, OptionalInt.of(4), "a").stream())
               .collect(Collectors.toList());
       assertEquals(1, retraceFrameElements.size());
 
       RetraceFrameElement retraceFrameElement = retraceFrameElements.get(0);
       // Check that rewriting the frames will remove the top 1 frames if the condition is active.
       Map<Integer, RetracedMethodReference> results = new LinkedHashMap<>();
-      retraceFrameElement.visitRewrittenFrames(
-          context,
-          (methodReference, index) -> {
-            RetracedMethodReference existingValue = results.put(index, methodReference);
-            assertNull(existingValue);
-          });
+      retraceFrameElement
+          .streamRewritten(throwingContext)
+          .forEach(
+              frame -> {
+                RetracedMethodReference existingValue =
+                    results.put(frame.getIndex(), frame.getMethodReference());
+                assertNull(existingValue);
+              });
       assertEquals(1, results.size());
       assertEquals(7, results.get(0).getOriginalPositionOrDefault(4));
       assertEquals(results.get(0).getMethodName(), "caller");
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java
index 376a274..b4c2595 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedFrameTest.java
@@ -13,10 +13,10 @@
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
 import com.android.tools.r8.retrace.Retracer;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
+import java.util.OptionalInt;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,17 +50,24 @@
                   ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
               .retraceClass(Reference.classFromTypeName("a"))
               .stream()
-              .flatMap(element -> element.lookupFrame(Optional.of(3), "a").stream())
+              .flatMap(
+                  element ->
+                      element
+                          .lookupFrame(RetraceStackTraceContext.empty(), OptionalInt.of(3), "a")
+                          .stream())
               .collect(Collectors.toList());
       assertEquals(1, frameResults.size());
       RetraceFrameElement retraceFrameElement = frameResults.get(0);
-      List<RetracedMethodReference> allFrames = new ArrayList<>();
-      retraceFrameElement.visitAllFrames((method, ignored) -> allFrames.add(method));
+      List<RetracedMethodReference> allFrames =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
       assertEquals(2, allFrames.size());
-      List<RetracedMethodReference> nonSyntheticFrames = new ArrayList<>();
-      retraceFrameElement.visitRewrittenFrames(
-          RetraceStackTraceContext.getInitialContext(),
-          (method, ignored) -> nonSyntheticFrames.add(method));
+      List<RetracedMethodReference> nonSyntheticFrames =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
       assertEquals(1, nonSyntheticFrames.size());
       assertEquals(nonSyntheticFrames.get(0), allFrames.get(0));
       assertEquals("mango", allFrames.get(1).getMethodName());
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java
index c7b8d7a..1b10739 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSynthesizedInnerFrameTest.java
@@ -13,10 +13,10 @@
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
 import com.android.tools.r8.retrace.Retracer;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
+import java.util.OptionalInt;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,17 +50,24 @@
                   ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {})
               .retraceClass(Reference.classFromTypeName("a"))
               .stream()
-              .flatMap(element -> element.lookupFrame(Optional.of(3), "a").stream())
+              .flatMap(
+                  element ->
+                      element
+                          .lookupFrame(RetraceStackTraceContext.empty(), OptionalInt.of(3), "a")
+                          .stream())
               .collect(Collectors.toList());
       assertEquals(1, frameResults.size());
       RetraceFrameElement retraceFrameElement = frameResults.get(0);
-      List<RetracedMethodReference> allFrames = new ArrayList<>();
-      retraceFrameElement.visitAllFrames((method, ignored) -> allFrames.add(method));
+      List<RetracedMethodReference> allFrames =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
       assertEquals(2, allFrames.size());
-      List<RetracedMethodReference> nonSyntheticFrames = new ArrayList<>();
-      retraceFrameElement.visitRewrittenFrames(
-          RetraceStackTraceContext.getInitialContext(),
-          (method, ignored) -> nonSyntheticFrames.add(method));
+      List<RetracedMethodReference> nonSyntheticFrames =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
       assertEquals(allFrames, nonSyntheticFrames);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java
index 98ccb02..3bbb842 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java
@@ -47,7 +47,7 @@
           RetraceApiUnknownJsonTest.ApiTest.class,
           RetraceApiRewriteFrameInlineNpeTest.ApiTest.class);
   public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of();
+      ImmutableList.of(RetraceApiRewriteFrameInlineNpeResidualTest.ApiTest.class);
 
   public static void runJunitOnTests(
       CfRuntime runtime,
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/DifferentLineNumberSpanStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/DifferentLineNumberSpanStackTrace.java
new file mode 100644
index 0000000..16a2725
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/DifferentLineNumberSpanStackTrace.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class DifferentLineNumberSpanStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat a.a(Unknown Source:1)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> a:",
+        "  1:1:void method1(java.lang.String):42:44 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        // TODO(b/201042571): Should be ambiguous or have line number removed
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            // TODO(b/201042571): Should be ambiguous or have line number removed
+            + " method1(java.lang.String)(Main.java:42)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleOriginalLinesNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleOriginalLinesNoLineNumberStackTrace.java
new file mode 100644
index 0000000..d4688c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleOriginalLinesNoLineNumberStackTrace.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class MultipleOriginalLinesNoLineNumberStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException", "\tat a.a(Unknown Source)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> a:",
+        "    void method1(java.lang.String):42:43 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        // TODO(b/201042571): Should not report a line number
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            // TODO(b/201042571): Should not report a line number
+            + " method1(java.lang.String)(Main.java:42)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 76e8f34..b666291 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -60,6 +60,7 @@
         .addKeepMainRule(Ordinals.class)
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
         .enableSideEffectAnnotations()
         .addOptionsModification(this::configure)
         .setMinApi(parameters.getApiLevel())
@@ -111,6 +112,7 @@
         .addKeepMainRule(Names.class)
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
         .enableSideEffectAnnotations()
         .addOptionsModification(this::configure)
         .setMinApi(parameters.getApiLevel())
@@ -159,6 +161,7 @@
         .addKeepMainRule(ToStrings.class)
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
         .enableSideEffectAnnotations()
         .addOptionsModification(this::configure)
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index 10a70c4a..7eeae7c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import java.util.concurrent.TimeUnit;
 
 class Names {
@@ -23,12 +24,14 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String simple() {
     return Number.TWO.name();
   }
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String local() {
     Number two = Number.TWO;
     return two.name();
@@ -36,6 +39,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String multipleUsages() {
     Number two = Number.TWO;
     return two.ordinal() + two.name();
@@ -43,6 +47,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String inlined() {
     return inlined2(Number.TWO);
   }
@@ -58,12 +63,14 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String differentTypeStaticField() {
     return Number.DOWN.name();
   }
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String nonValueStaticField() {
     return Number.DEFAULT.name();
   }
@@ -80,6 +87,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String nonStaticGet() {
     return new Names().two.name();
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
index de4677d..79aaeaa 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import java.util.concurrent.TimeUnit;
 
 class Ordinals {
@@ -34,6 +35,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String multipleUsages() {
     Number two = Number.TWO;
     return two.name() + two.ordinal();
@@ -50,6 +52,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static int inSwitch() {
     // Unlike normal invocations of Enum.ordinal(), a switch on enum will emit an invoke-virtual
     // where the receiver is the enum subtype and not java.lang.Enum. This test ensures that case
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
index 957a5e1..c518868 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
@@ -48,6 +49,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String valueWithToString() {
     return ValueToString.ONE.toString();
   }
@@ -59,18 +61,21 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String noToString() {
     return NoToString.TWO.toString();
   }
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String local() {
     NoToString two = NoToString.TWO;
     return two.toString();
   }
 
   @NeverInline
+  @NeverPropagateValue
   private static String multipleUsages() {
     NoToString two = NoToString.TWO;
     // Side-effect instead of concatenation avoids two toString calls.
@@ -80,6 +85,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String inlined() {
     return inlined2(NoToString.TWO);
   }
@@ -95,12 +101,14 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String differentTypeStaticField() {
     return NoToString.DOWN.toString();
   }
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String nonValueStaticField() {
     return NoToString.DEFAULT.toString();
   }
@@ -117,6 +125,7 @@
 
   @AssumeMayHaveSideEffects
   @NeverInline
+  @NeverPropagateValue
   private static String nonStaticGet() {
     return new ToStrings().two.toString();
   }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index bc72103..a38f6f2 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -619,6 +619,40 @@
         });
   }
 
+  public ClassFileTransformer setFieldType(FieldPredicate predicate, String newFieldType) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            if (predicate.test(access, name, descriptor, signature, value)) {
+              String newDescriptor = DescriptorUtils.javaTypeToDescriptor(newFieldType);
+              return super.visitField(access, name, newDescriptor, signature, value);
+            }
+            return super.visitField(access, name, descriptor, signature, value);
+          }
+        });
+  }
+
+  public ClassFileTransformer setReturnType(MethodPredicate predicate, String newReturnType) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            if (predicate.test(access, name, descriptor, signature, exceptions)) {
+              String oldDescriptorExcludingReturnType =
+                  descriptor.substring(0, descriptor.lastIndexOf(')') + 1);
+              String newDescriptor =
+                  oldDescriptorExcludingReturnType
+                      + DescriptorUtils.javaTypeToDescriptor(newReturnType);
+              return super.visitMethod(access, name, newDescriptor, signature, exceptions);
+            }
+            return super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
   public ClassFileTransformer setGenericSignature(MethodPredicate predicate, String newSignature) {
     return addClassTransformer(
         new ClassTransformer() {
@@ -721,6 +755,13 @@
         MethodVisitor visitor);
   }
 
+  /** Abstraction of the MethodVisitor.visitFieldInsn method with its sub visitor. */
+  @FunctionalInterface
+  public interface FieldInsnTransform {
+    void visitFieldInsn(
+        int opcode, String owner, String name, String descriptor, MethodVisitor visitor);
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
   @FunctionalInterface
   public interface MethodInsnTransform {
@@ -945,6 +986,41 @@
   }
 
   @FunctionalInterface
+  private interface VisitFieldInsnCallback {
+    void visitFieldInsn(int opcode, String owner, String name, String descriptor);
+  }
+
+  private MethodVisitor redirectVisitFieldInsn(
+      MethodVisitor visitor, VisitFieldInsnCallback callback) {
+    return new MethodVisitor(ASM7, visitor) {
+      @Override
+      public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
+        callback.visitFieldInsn(opcode, owner, name, descriptor);
+      }
+    };
+  }
+
+  public ClassFileTransformer transformFieldInsnInMethod(
+      String methodName, FieldInsnTransform transform) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
+            if (getContext().method.getMethodName().equals(methodName)) {
+              transform.visitFieldInsn(
+                  opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  redirectVisitFieldInsn(this, super::visitFieldInsn));
+            } else {
+              super.visitMethodInsn(opcode, owner, name, descriptor);
+            }
+          }
+        });
+  }
+
+  @FunctionalInterface
   private interface VisitMethodInsnCallback {
     void visitMethodInsn(
         int opcode, String owner, String name, String descriptor, boolean isInterface);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 36d9ed5..4e2b27d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.Retracer;
 
 public interface InstructionSubject {
@@ -162,10 +163,11 @@
   }
 
   default RetraceFrameResult retraceLinePosition(Retracer retracer) {
-    return retrace(retracer).narrowByPosition(getLineNumber());
+    return retrace(retracer).narrowByPosition(RetraceStackTraceContext.empty(), getLineNumber());
   }
 
   default RetraceFrameResult retracePcPosition(Retracer retracer, MethodSubject methodSubject) {
-    return retrace(retracer).narrowByPosition(getOffset(methodSubject).offset);
+    return retrace(retracer)
+        .narrowByPosition(RetraceStackTraceContext.empty(), getOffset(methodSubject).offset);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index edeed5a..8b54ae2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.RetracedMethodReference;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.Visibility;
 import com.google.common.collect.ImmutableList;
@@ -588,27 +589,29 @@
         RetraceFrameElement single = item.stream().collect(Collectors.toSingle());
         Box<LinePosition> currentPosition = new Box<>(startPosition);
         Box<Boolean> returnValue = new Box<>();
-        single.visitAllFrames(
-            (method, __) -> {
-              LinePosition currentInline = currentPosition.get();
-              if (currentInline == null) {
-                returnValue.set(false);
-                return;
-              }
-              if (method.isUnknown()) {
-                returnValue.set(false);
-                return;
-              }
-              boolean sameMethod =
-                  method.asKnown().getMethodReference().equals(currentInline.methodReference);
-              boolean samePosition =
-                  method.getOriginalPositionOrDefault(currentInline.minifiedPosition)
-                      == currentInline.originalPosition;
-              if (!returnValue.isSet() || returnValue.get()) {
-                returnValue.set(sameMethod & samePosition);
-              }
-              currentPosition.set(currentInline.caller);
-            });
+        single.stream()
+            .forEach(
+                frame -> {
+                  LinePosition currentInline = currentPosition.get();
+                  if (currentInline == null) {
+                    returnValue.set(false);
+                    return;
+                  }
+                  RetracedMethodReference method = frame.getMethodReference();
+                  if (method.isUnknown()) {
+                    returnValue.set(false);
+                    return;
+                  }
+                  boolean sameMethod =
+                      method.asKnown().getMethodReference().equals(currentInline.methodReference);
+                  boolean samePosition =
+                      method.getOriginalPositionOrDefault(currentInline.minifiedPosition)
+                          == currentInline.originalPosition;
+                  if (!returnValue.isSet() || returnValue.get()) {
+                    returnValue.set(sameMethod & samePosition);
+                  }
+                  currentPosition.set(currentInline.caller);
+                });
         return returnValue.isSet() && returnValue.get();
       }
 
@@ -625,18 +628,17 @@
       @Override
       protected boolean matchesSafely(RetraceFrameResult item) {
         RetraceFrameElement single = item.stream().collect(Collectors.toSingle());
-        Box<Boolean> matches = new Box<>(true);
-        single.visitAllFrames(
-            (method, index) -> {
-              StackTraceLine stackTraceLine = stackTrace.get(index);
-              if (!stackTraceLine.methodName.equals(method.getMethodName())
-                  || !stackTraceLine.className.equals(method.getHolderClass().getTypeName())
-                  || stackTraceLine.lineNumber
-                      != method.getOriginalPositionOrDefault(minifiedPositions.get(index))) {
-                matches.set(false);
-              }
-            });
-        return matches.get();
+        return !single.stream()
+            .anyMatch(
+                frame -> {
+                  StackTraceLine stackTraceLine = stackTrace.get(frame.getIndex());
+                  RetracedMethodReference method = frame.getMethodReference();
+                  return !stackTraceLine.methodName.equals(method.getMethodName())
+                      || !stackTraceLine.className.equals(method.getHolderClass().getTypeName())
+                      || stackTraceLine.lineNumber
+                          != method.getOriginalPositionOrDefault(
+                              minifiedPositions.get(frame.getIndex()));
+                });
       }
 
       @Override
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index 7fbd228..6b09100 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-6b7ccd6aa40c22bea72017bfabca022d4d90d70a
\ No newline at end of file
+e7ea0fbb97ccfb2faa89b58d60f8b54f5bca0130
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index e8070da..504496e 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -209,12 +209,21 @@
     return dump.version()
   return args.version
 
-def determine_compiler(args, dump):
+def determine_compiler(args, build_properties):
   compilers = ['d8', 'r8', 'r8full', 'l8']
-  if args.compiler not in compilers:
+  compiler = args.compiler
+  if not compiler and 'tool' in build_properties:
+    compiler = build_properties.get('tool').lower()
+    if (compiler == 'r8'):
+      if not 'force-proguard-compatibility' in build_properties:
+        error("Unable to determine R8 compiler variant from build.properties."
+              " No value for 'force-proguard-compatibility'.")
+      if build_properties.get('force-proguard-compatibility').lower() == 'false':
+        compiler = compiler + 'full'
+  if compiler not in compilers:
     error("Unable to determine a compiler to use. Specified %s,"
           " Valid options: %s" % (args.compiler, ', '.join(compilers)))
-  return args.compiler
+  return compiler
 
 def determine_output(args, temp):
   return os.path.join(temp, 'out.jar')
@@ -295,7 +304,7 @@
       print("WARNING: Unexpected lack of library classes in dump")
     build_properties = determine_build_properties(args, dump)
     version = determine_version(args, dump)
-    compiler = determine_compiler(args, dump)
+    compiler = determine_compiler(args, build_properties)
     out = determine_output(args, temp)
     min_api = determine_min_api(args, build_properties)
     classfile = determine_class_file(args, build_properties)
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 0446d64..f9d14f9 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -602,7 +602,7 @@
 
   redir_command = ("/google/data/ro/teams/android-devtools-infra/tools/redir "
                  + "--alsologtostderr "
-                 + "--gcs_bucket_path=/studio_staging/maven2/${USER}/%s "
+                 + "--gcs_bucket_path=/bigstore/gmaven-staging/${USER}/%s "
                  + "--port=1480") % release_id
 
   get_command = ("mvn org.apache.maven.plugins:maven-dependency-plugin:2.4:get "
@@ -620,6 +620,10 @@
 repositories {
   maven {
     url 'http://localhost:1480'
+    allowInsecureProtocol true
+  }
+  dependencies {
+    classpath '%s'  // Must be before the Gradle Plugin for Android.
   }
 }
 
@@ -627,7 +631,7 @@
 
 rm -rf /tmp/maven_repo_local
 %s
-""" % (redir_command, get_command))
+""" % (redir_command, artifact, get_command))
 
 
 def gmaven_publisher_publish(args, release_id):
diff --git a/tools/retrace.py b/tools/retrace.py
index d28c222..94ec1b3 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -55,6 +55,11 @@
       default=None,
       help='Sets a custom regular expression used for parsing'
   )
+  parser.add_argument(
+      '--verbose',
+      default=None,
+      action='store_true',
+      help='Enables verbose retracing.')
   return parser.parse_args()
 
 
@@ -68,9 +73,10 @@
       args.no_r8lib,
       quiet=args.quiet,
       debug=args.debug_agent,
-      regex=args.regex)
+      regex=args.regex,
+      verbose=args.verbose)
 
-def run(map_path, stacktrace, no_r8lib, quiet=False, debug=False, regex=None):
+def run(map_path, stacktrace, no_r8lib, quiet=False, debug=False, regex=None, verbose=False):
   retrace_args = [jdk.GetJavaExecutable()]
 
   if debug:
@@ -94,6 +100,9 @@
   if stacktrace:
     retrace_args.append(stacktrace)
 
+  if verbose:
+    retrace_args.append('--verbose')
+
   utils.PrintCmd(retrace_args, quiet=quiet)
   return subprocess.call(retrace_args)
 
diff --git a/tools/test.py b/tools/test.py
index c719ea2..6f27a56 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -39,7 +39,7 @@
 # Should be short enough that we ensure that two calls are close enough
 # to happen before bot times out.
 # A false positiv, i.e., printing the stacks of non hanging processes
-# is not a problem, no harm done except some logging in the stdout.
+# is not a problem, no harm done except some logging in stdout.
 TIMEOUT_HANDLER_PERIOD = 60 * 18
 
 BUCKET = 'r8-test-results'