Merge commit '021c3f09703d17fec5022a007125dbf3e06a285e' into dev-release
diff --git a/.gitignore b/.gitignore
index f16c1ba..80c9662 100644
--- a/.gitignore
+++ b/.gitignore
@@ -131,6 +131,12 @@
 third_party/openjdk/desugar_jdk_libs_releases/1.1.1.tar.gz
 third_party/openjdk/desugar_jdk_libs_releases/1.1.5
 third_party/openjdk/desugar_jdk_libs_releases/1.1.5.tar.gz
+third_party/openjdk/jdk-16/linux
+third_party/openjdk/jdk-16/linux.tar.gz
+third_party/openjdk/jdk-16/osx
+third_party/openjdk/jdk-16/osx.tar.gz
+third_party/openjdk/jdk-16/windows
+third_party/openjdk/jdk-16/windows.tar.gz
 third_party/openjdk/jdk-15/linux
 third_party/openjdk/jdk-15/linux.tar.gz
 third_party/openjdk/jdk-15/osx
diff --git a/build.gradle b/build.gradle
index 17206b1..4a8dd81 100644
--- a/build.gradle
+++ b/build.gradle
@@ -131,9 +131,9 @@
             srcDirs = ['src/test/examplesJava11']
         }
     }
-    examplesJava15 {
+    examplesJava16 {
         java {
-            srcDirs = ['src/test/examplesJava15']
+            srcDirs = ['src/test/examplesJava16']
         }
     }
     jdk11TimeTests {
@@ -372,18 +372,18 @@
                 "third_party": ["openjdk/openjdk-9.0.4/linux",
                                 "openjdk/jdk8/linux-x86",
                                 "openjdk/jdk-11/linux",
-                                "openjdk/jdk-15/linux"],
+                                "openjdk/jdk-16/linux"],
         ],
         osx: [
                 "third_party": ["openjdk/openjdk-9.0.4/osx",
                                 "openjdk/jdk8/darwin-x86",
                                 "openjdk/jdk-11/osx",
-                                "openjdk/jdk-15/osx"],
+                                "openjdk/jdk-16/osx"],
         ],
         windows: [
                 "third_party": ["openjdk/openjdk-9.0.4/windows",
                                 "openjdk/jdk-11/windows",
-                                "openjdk/jdk-15/windows"],
+                                "openjdk/jdk-16/windows"],
         ],
 ]
 
@@ -575,12 +575,15 @@
             options.compilerArgs.add('--enable-preview')
         }
         if (OperatingSystem.current().isLinux()) {
+            dependsOn getDownloadDepsTaskName("third_party", "openjdk/" + javaHome + "/linux")
             options.forkOptions.javaHome = file(jdkDir + 'linux')
         } else if (OperatingSystem.current().isMacOsX()) {
+            dependsOn getDownloadDepsTaskName("third_party", "openjdk/" + javaHome + "/osx")
             options.forkOptions.javaHome = compatibility > JavaVersion.VERSION_1_9
                     ? file(jdkDir + 'osx/Contents/Home')
                     : file(jdkDir + 'osx')
         } else {
+            dependsOn getDownloadDepsTaskName("third_party", "openjdk/" + javaHome + "/windows")
             options.forkOptions.javaHome = file(jdkDir + 'windows')
         }
         sourceCompatibility = compatibility
@@ -619,9 +622,9 @@
         JavaVersion.VERSION_11,
         false)
 setJdkCompilationWithCompatibility(
-        sourceSets.examplesJava15.compileJavaTaskName,
-        'jdk-15',
-        JavaVersion.VERSION_15,
+        sourceSets.examplesJava16.compileJavaTaskName,
+        'jdk-16',
+        JavaVersion.VERSION_16,
         true)
 
 task compileMainWithJava11 (type: JavaCompile) {
@@ -1591,7 +1594,7 @@
 buildExampleJarsCreateTask("Java9", sourceSets.examplesJava9)
 buildExampleJarsCreateTask("Java10", sourceSets.examplesJava10)
 buildExampleJarsCreateTask("Java11", sourceSets.examplesJava11)
-buildExampleJarsCreateTask("Java15", sourceSets.examplesJava15)
+buildExampleJarsCreateTask("Java16", sourceSets.examplesJava16)
 
 task provideArtFrameworksDependencies {
     cloudDependencies.tools.forEach({ art ->
@@ -1674,7 +1677,7 @@
     dependsOn buildExampleJava9Jars
     dependsOn buildExampleJava10Jars
     dependsOn buildExampleJava11Jars
-    dependsOn buildExampleJava15Jars
+    dependsOn buildExampleJava16Jars
     dependsOn buildExampleAndroidApi
     def examplesDir = file("src/test/examples")
     def noDexTests = [
@@ -2046,7 +2049,9 @@
     // Hide all test events from the console, they are written to the report.
     task.testLogging { events = [] }
 
-    def branch = getGitBranchName()
+    def branch = project.hasProperty('testing-state-name')
+        ? project.getProperty('testing-state-name')
+        : getGitBranchName()
     def reportDir = file("${buildDir}/test-state/${branch}")
     def index = reportDir.toPath().resolve("index.html").toFile()
     def resetState = project.hasProperty('reset-testing-state')
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 8caf056..b881778 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -40,6 +40,28 @@
       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\""
+      }
+      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"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -413,6 +435,54 @@
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     }
     builders {
+      name: "linux-d8_jctf"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "jctf: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: "dex_vm:\"all\""
+        properties_j: "only_jctf:\"true\""
+        properties_j: "tool:\"d8\""
+      }
+      priority: 26
+      execution_timeout_secs: 43200
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    }
+    builders {
+      name: "linux-d8_jctf_release"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "jctf: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: "dex_vm:\"all\""
+        properties_j: "only_jctf:\"true\""
+        properties_j: "tool:\"d8\""
+      }
+      priority: 26
+      execution_timeout_secs: 43200
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    }
+    builders {
       name: "linux-dex_default"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -677,6 +747,54 @@
       service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
     }
     builders {
+      name: "linux-r8cf_jctf"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "jctf: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: "dex_vm:\"all\""
+        properties_j: "only_jctf:\"true\""
+        properties_j: "tool:\"r8cf\""
+      }
+      priority: 26
+      execution_timeout_secs: 43200
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    }
+    builders {
+      name: "linux-r8cf_jctf_release"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "jctf: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: "dex_vm:\"all\""
+        properties_j: "only_jctf:\"true\""
+        properties_j: "tool:\"r8cf\""
+      }
+      priority: 26
+      execution_timeout_secs: 43200
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    }
+    builders {
       name: "windows"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index 112e9a1..58474a2 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -11,6 +11,26 @@
   refs: "regexp: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/archive"
     category: "R8"
     short_name: "archive"
@@ -21,6 +41,11 @@
     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"
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg
index 5267938..c441677 100644
--- a/infra/config/global/generated/luci-notify.cfg
+++ b/infra/config/global/generated/luci-notify.cfg
@@ -228,6 +228,30 @@
   }
   builders {
     bucket: "ci"
+    name: "linux-d8_jctf"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-d8_jctf_release"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
     name: "linux-dex_default"
     repository: "https://r8.googlesource.com/r8"
   }
@@ -372,6 +396,30 @@
   }
   builders {
     bucket: "ci"
+    name: "linux-r8cf_jctf"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-r8cf_jctf_release"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
     name: "windows"
     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 4aa3c0f..b4ee1f0 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -19,6 +19,20 @@
   }
 }
 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"
   acl_sets: "ci"
   triggering_policy {
@@ -177,6 +191,24 @@
   }
 }
 job {
+  id: "linux-d8_jctf"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-d8_jctf"
+  }
+}
+job {
+  id: "linux-d8_jctf_release"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-d8_jctf_release"
+  }
+}
+job {
   id: "linux-dex_default"
   acl_sets: "ci"
   buildbucket {
@@ -295,6 +327,24 @@
   }
 }
 job {
+  id: "linux-r8cf_jctf"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-r8cf_jctf"
+  }
+}
+job {
+  id: "linux-r8cf_jctf_release"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-r8cf_jctf_release"
+  }
+}
+job {
   id: "windows"
   acl_sets: "ci"
   buildbucket {
@@ -324,12 +374,14 @@
   triggers: "linux-android=10.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"
   triggers: "linux-jdk11_release"
   triggers: "linux-jdk8_release"
   triggers: "linux-jdk9_release"
   triggers: "linux-none_release"
+  triggers: "linux-r8cf_jctf_release"
   triggers: "windows_release"
   gitiles {
     repo: "https://r8.googlesource.com/r8"
@@ -349,12 +401,14 @@
   triggers: "linux-android=10.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-none"
+  triggers: "linux-r8cf_jctf"
   triggers: "windows"
   gitiles {
     repo: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 77d9887..e78b09a 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -107,9 +107,9 @@
     dimensions["normal"] = "true"
   return dimensions
 
-def r8_builder(name, priority=26, **kwargs):
+def r8_builder(name, priority=26, trigger=True, **kwargs):
   release = name.endswith("release")
-  triggered = ["branch-gitiles-trigger"] if release \
+  triggered = None if not trigger else ["branch-gitiles-trigger"] if release\
       else ["main-gitiles-trigger"]
 
   luci.builder(
@@ -119,7 +119,7 @@
         "iam.gserviceaccount.com",
     build_numbers = True,
     swarming_tags = ["vpython:native-python-wrapper"],
-    notifies = ["r8-failures"],
+    notifies = ["r8-failures"] if trigger else None,
     priority = priority,
     triggered_by = triggered,
     executable = "rex",
@@ -137,8 +137,8 @@
   for name in [name, name + "_release"]:
     r8_builder(
         name = name,
-        execution_timeout=execution_timeout,
-        expiration_timeout=expiration_timeout,
+        execution_timeout = execution_timeout,
+        expiration_timeout = expiration_timeout,
         dimensions = dimensions,
         properties = {
             "test_options" : test_options,
@@ -149,8 +149,36 @@
 def r8_tester_with_default(name, test_options, dimensions=None):
   r8_tester(name, test_options + common_test_options, dimensions)
 
+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"]:
+  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(),
@@ -160,11 +188,9 @@
             max_concurrent_invocations = 3
         ),
         priority = 25,
-        properties = {
-            "archive": "true",
-            "builder_group" : "internal.client.r8"
-        },
-        execution_timeout = time.minute * 30,
+        trigger = not desugar,
+        properties = properties,
+        execution_timeout = time.hour * 1 if desugar else time.minute * 30 ,
         expiration_timeout = time.hour * 35,
     )
 archivers()
diff --git a/scripts/add-openjdk.sh b/scripts/add-openjdk.sh
new file mode 100644
index 0000000..8587b93
--- /dev/null
+++ b/scripts/add-openjdk.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+#
+# 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.
+
+set -e
+set -x
+
+echo "Update this script manually before using"
+exit -1
+
+# Download JDK from https://jdk.java.net/X/ (X = version) into ~/Downloads
+# Create directory third_party/openjdk/jdk-X
+# cd into third_party/openjdk/jdk-X
+# Prepare README.google
+# Update JDK_VERSION below
+
+# Now run script wit fingers crossed!
+
+JDK_VERSION=16.0.2
+
+tar xf ~/Downloads/openjdk-${JDK_VERSION}_linux-x64_bin.tar.gz
+cp -rL jdk-${JDK_VERSION} linux
+cp README.google linux
+upload_to_google_storage.py -a --bucket r8-deps linux
+rm -rf jdk-${JDK_VERSION}
+rm -rf linux
+rm linux.tar.gz
+
+tar xf ~/Downloads/openjdk-${JDK_VERSION}_osx-x64_bin.tar.gz
+cp -rL jdk-${JDK_VERSION}.jdk osx
+cp README.google osx
+upload_to_google_storage.py -a --bucket r8-deps osx
+rm -rf osx
+rm -rf jdk-${JDK_VERSION}.jdk
+rm osx.tar.gz
+
+unzip ~/Downloads/openjdk-${JDK_VERSION}_windows-x64_bin.zip
+cp -rL jdk-${JDK_VERSION} windows
+cp README.google windows
+upload_to_google_storage.py -a --bucket r8-deps windows
+rm -rf windows
+rm -rf jdk-${JDK_VERSION}
+rm windows.tar.gz
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c23b48c..671fb6b 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -454,6 +454,9 @@
       internal.enableSwitchRewriting = false;
       assert internal.enableStringSwitchConversion;
       internal.enableStringSwitchConversion = false;
+    } else {
+      assert !internal.desugarSpecificOptions().allowAllDesugaredInput
+          || getDesugarState() == DesugarState.OFF;
     }
     internal.mainDexListConsumer = getMainDexListConsumer();
     internal.minimalMainDex = internal.debug || minimalMainDex;
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 295d52d..8d73909 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import java.io.PrintStream;
 
 /**
  * A DiagnosticsHandler can be provided to customize handling of diagnostics information.
@@ -14,22 +15,27 @@
 @Keep
 public interface DiagnosticsHandler {
 
+  /** Should be considered private. */
+  static void printDiagnosticToStream(Diagnostic diagnostic, String prefix, PrintStream stream) {
+    if (diagnostic.getOrigin() != Origin.unknown()) {
+      stream.print(prefix + " in " + diagnostic.getOrigin());
+      if (diagnostic.getPosition() != Position.UNKNOWN) {
+        stream.print(" at " + diagnostic.getPosition().getDescription());
+      }
+      stream.println(":");
+    } else {
+      stream.print(prefix + ": ");
+    }
+    stream.println(diagnostic.getDiagnosticMessage());
+  }
+
   /**
    * Handle error diagnostics.
    *
    * @param error Diagnostic containing error information.
    */
   default void error(Diagnostic error) {
-    if (error.getOrigin() != Origin.unknown()) {
-      System.err.print("Error in " + error.getOrigin());
-      if (error.getPosition() != Position.UNKNOWN) {
-        System.err.print(" at " + error.getPosition().getDescription());
-      }
-      System.err.println(":");
-    } else {
-      System.err.print("Error: ");
-    }
-    System.err.println(error.getDiagnosticMessage());
+    printDiagnosticToStream(error, "Error", System.err);
   }
 
   /**
@@ -38,13 +44,7 @@
    * @param warning Diagnostic containing warning information.
    */
   default void warning(Diagnostic warning) {
-    if (warning.getOrigin() != Origin.unknown()) {
-      System.err.println("Warning in " + warning.getOrigin() + ":");
-      System.err.print("  ");
-    } else {
-      System.err.print("Warning: ");
-    }
-    System.err.println(warning.getDiagnosticMessage());
+    printDiagnosticToStream(warning, "Warning", System.err);
   }
 
   /**
@@ -53,13 +53,7 @@
    * @param info Diagnostic containing the information.
    */
   default void info(Diagnostic info) {
-    if (info.getOrigin() != Origin.unknown()) {
-      System.out.println("Info in " + info.getOrigin() + ":");
-      System.out.print("  ");
-    } else {
-      System.out.print("Info: ");
-    }
-    System.out.println(info.getDiagnosticMessage());
+    printDiagnosticToStream(info, "Info", System.out);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index dc284ed..cba8b55 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -23,8 +23,8 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.MainDexInfo;
@@ -105,7 +105,7 @@
 
     @Override
     public void registerInvokeVirtual(DexMethod method) {
-      ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
+      MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
       DexEncodedMethod target =
           resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
       if (target != null && target.getReference() != method) {
@@ -277,7 +277,7 @@
       // If clazz overrides any methods in superType, we should keep those as well.
       clazz.forEachMethod(
           method -> {
-            ResolutionResult resolutionResult =
+            MethodResolutionResult resolutionResult =
                 appInfo.resolveMethodOn(
                     superType, method.getReference(), superType != clazz.superType);
             DexEncodedMethod dexEncodedMethod = resolutionResult.getSingleTarget();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6f2675f..22308ef 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -367,9 +367,9 @@
                 subtypingInfo,
                 classMergingEnqueuerExtensionBuilder);
 
-        assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
-        assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
-        assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
+        assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness);
+        assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness);
+        assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness);
         assert appView.rootSet().verifyKeptItemsAreKept(appView);
         appView.rootSet().checkAllRulesAreUsed(options);
 
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index ddc81b6..0460075 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.bisect.Bisect;
+import com.android.tools.r8.cf.CfVerifierTool;
 import com.android.tools.r8.compatproguard.CompatProguard;
 import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.relocator.RelocatorCommandLine;
@@ -74,10 +75,13 @@
         BackportedMethodList.main(shift(args));
         break;
       case "relocator":
-        RelocatorCommandLine.main(shift((args)));
+        RelocatorCommandLine.main(shift(args));
         break;
       case "tracereferences":
-        TraceReferences.main(shift((args)));
+        TraceReferences.main(shift(args));
+        break;
+      case "verify":
+        CfVerifierTool.main(shift(args));
         break;
       default:
         runDefault(args);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
index ee0ffb2..ed6767a 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.androidapi;
 
+import static com.android.tools.r8.utils.AndroidApiLevel.getAndroidApiLevel;
+
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -28,38 +30,56 @@
 
   public abstract int getMemberCount();
 
-  public abstract TraversalContinuation visitFields(
-      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor);
+  public TraversalContinuation visitFields(
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    return visitFields(visitor, classReference, 1);
+  }
 
-  public abstract TraversalContinuation visitMethods(
-      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor);
+  public TraversalContinuation visitMethods(
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+    return visitMethods(visitor, classReference, 1);
+  }
+
+  protected abstract TraversalContinuation visitFields(
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+      ClassReference holder,
+      int minApiClass);
+
+  protected abstract TraversalContinuation visitMethods(
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+      ClassReference holder,
+      int minApiClass);
 
   protected TraversalContinuation visitField(
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+      ClassReference holder,
+      int minApiClass,
+      int minApiField,
       String name,
-      String typeDescriptor,
-      int apiLevel,
-      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+      String typeDescriptor) {
     return visitor.apply(
-        Reference.field(classReference, name, Reference.typeFromDescriptor(typeDescriptor)),
-        AndroidApiLevel.getAndroidApiLevel(apiLevel));
+        Reference.field(holder, name, Reference.typeFromDescriptor(typeDescriptor)),
+        getAndroidApiLevel(Integer.max(minApiClass, minApiField)));
   }
 
   protected TraversalContinuation visitMethod(
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+      ClassReference holder,
+      int minApiClass,
+      int minApiMethod,
       String name,
       String[] formalTypeDescriptors,
-      String returnType,
-      int apiLevel,
-      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+      String returnType) {
     List<TypeReference> typeReferenceList = new ArrayList<>(formalTypeDescriptors.length);
     for (String formalTypeDescriptor : formalTypeDescriptors) {
       typeReferenceList.add(Reference.typeFromDescriptor(formalTypeDescriptor));
     }
     return visitor.apply(
         Reference.method(
-            classReference,
+            holder,
             name,
             typeReferenceList,
             returnType == null ? null : Reference.returnTypeFromDescriptor(returnType)),
-        AndroidApiLevel.getAndroidApiLevel(apiLevel));
+        getAndroidApiLevel(Integer.max(minApiClass, minApiMethod)));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java b/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
new file mode 100644
index 0000000..7bf11e0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/CfVerifierTool.java
@@ -0,0 +1,42 @@
+// 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.cf;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidApp.Builder;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.nio.file.Paths;
+
+/** Tool to verify various aspects of class file inputs. */
+public class CfVerifierTool {
+
+  public static void main(String[] args) throws IOException {
+    Builder builder = AndroidApp.builder();
+    for (String arg : args) {
+      builder.addProgramFile(Paths.get(arg));
+    }
+    InternalOptions options = new InternalOptions();
+    DexApplication dexApplication =
+        new ApplicationReader(builder.build(), options, Timing.empty()).read();
+    AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(dexApplication));
+    appView.setAppServices(AppServices.builder(appView).build());
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramMethod(
+          method ->
+              method
+                  .getDefinition()
+                  .getCode()
+                  .asCfCode()
+                  .verifyFrames(method.getDefinition(), appView, clazz.getOrigin()));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/CfVersion.java b/src/main/java/com/android/tools/r8/cf/CfVersion.java
index 47eebe9..5b877b8 100644
--- a/src/main/java/com/android/tools/r8/cf/CfVersion.java
+++ b/src/main/java/com/android/tools/r8/cf/CfVersion.java
@@ -25,10 +25,17 @@
   public static final CfVersion V9 = new CfVersion(Opcodes.V9);
   public static final CfVersion V10 = new CfVersion(Opcodes.V10);
   public static final CfVersion V11 = new CfVersion(Opcodes.V11);
+  public static final CfVersion V11_PREVIEW = new CfVersion(Opcodes.V11 | Opcodes.V_PREVIEW);
   public static final CfVersion V12 = new CfVersion(Opcodes.V12);
+  public static final CfVersion V12_PREVIEW = new CfVersion(Opcodes.V12 | Opcodes.V_PREVIEW);
   public static final CfVersion V13 = new CfVersion(Opcodes.V13);
+  public static final CfVersion V13_PREVIEW = new CfVersion(Opcodes.V13 | Opcodes.V_PREVIEW);
   public static final CfVersion V14 = new CfVersion(Opcodes.V14);
+  public static final CfVersion V14_PREVIEW = new CfVersion(Opcodes.V14 | Opcodes.V_PREVIEW);
   public static final CfVersion V15 = new CfVersion(Opcodes.V15);
+  public static final CfVersion V15_PREVIEW = new CfVersion(Opcodes.V15 | Opcodes.V_PREVIEW);
+  public static final CfVersion V16 = new CfVersion(Opcodes.V16);
+  public static final CfVersion V16_PREVIEW = new CfVersion(Opcodes.V16 | Opcodes.V_PREVIEW);
 
   private final int version;
 
@@ -47,7 +54,8 @@
     CfVersion.V12,
     CfVersion.V13,
     CfVersion.V14,
-    CfVersion.V15
+    CfVersion.V15,
+    CfVersion.V16
   };
 
   // Private constructor in case we want to canonicalize versions.
@@ -64,19 +72,25 @@
   }
 
   public int minor() {
-    return version >> 16;
+    return version >>> 16;
   }
 
   public int raw() {
     return version;
   }
 
+  public boolean isPreview() {
+    return minor() == Opcodes.V_PREVIEW >>> 16;
+  }
+
   private static void specify(StructuralSpecification<CfVersion, ?> spec) {
     spec.withInt(CfVersion::major).withInt(CfVersion::minor);
   }
 
   public static Iterable<CfVersion> rangeInclusive(CfVersion from, CfVersion to) {
     assert from.isLessThanOrEqualTo(to);
+    assert !from.isPreview() : "This method does not handle preview versions";
+    assert !to.isPreview() : "This method does not handle preview versions";
     return Arrays.stream(versions)
         .filter(version -> version.isGreaterThanOrEqualTo(from))
         .filter(version -> version.isLessThanOrEqualTo(to))
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 9b55af9..e6db3e1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -10,12 +10,12 @@
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.ArrayCloneMethodResult;
-import com.android.tools.r8.graph.ResolutionResult.ClassNotFoundResult;
-import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
-import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
-import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.ArrayCloneMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult.ClassNotFoundResult;
+import com.android.tools.r8.graph.MethodResolutionResult.IllegalAccessOrNoSuchMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult.IncompatibleClassResult;
+import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -595,7 +595,7 @@
    * <p>This is to overcome the shortcoming of the DEX file format that does not allow to encode the
    * kind of a method reference.
    */
-  public ResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) {
+  public MethodResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) {
     assert checkIfObsolete();
     DexType holder = method.holder;
     if (holder.isArrayType()) {
@@ -608,13 +608,13 @@
     return resolveMethodOn(definition, method);
   }
 
-  public ResolutionResult resolveMethod(DexMethod method, boolean isInterface) {
+  public MethodResolutionResult resolveMethod(DexMethod method, boolean isInterface) {
     return isInterface
         ? resolveMethodOnInterface(method.holder, method)
         : resolveMethodOnClass(method, method.holder);
   }
 
-  public ResolutionResult resolveMethodOn(DexClass holder, DexMethod method) {
+  public MethodResolutionResult resolveMethodOn(DexClass holder, DexMethod method) {
     return holder.isInterface()
         ? resolveMethodOnInterface(holder, method)
         : resolveMethodOnClass(method, holder);
@@ -631,7 +631,8 @@
    * @param isInterface Indicates if resolution is to be done according to class or interface.
    * @return The result of resolution.
    */
-  public ResolutionResult resolveMethodOn(DexType holder, DexMethod method, boolean isInterface) {
+  public MethodResolutionResult resolveMethodOn(
+      DexType holder, DexMethod method, boolean isInterface) {
     assert checkIfObsolete();
     return isInterface
         ? resolveMethodOnInterface(holder, method)
@@ -645,7 +646,7 @@
    * 10.7 of the Java Language Specification</a>. All invokations will have target java.lang.Object
    * except clone which has no target.
    */
-  private ResolutionResult resolveMethodOnArray(DexType holder, DexMethod method) {
+  private MethodResolutionResult resolveMethodOnArray(DexType holder, DexMethod method) {
     assert checkIfObsolete();
     assert holder.isArrayType();
     if (method.name == dexItemFactory().cloneMethodName) {
@@ -655,7 +656,7 @@
     }
   }
 
-  public ResolutionResult resolveMethodOnClass(DexMethod method) {
+  public MethodResolutionResult resolveMethodOnClass(DexMethod method) {
     return resolveMethodOnClass(method, method.holder);
   }
 
@@ -670,7 +671,7 @@
    * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the
    * resolved method is used as basis for dispatch.
    */
-  public ResolutionResult resolveMethodOnClass(DexMethod method, DexType holder) {
+  public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexType holder) {
     assert checkIfObsolete();
     if (holder.isArrayType()) {
       return resolveMethodOnArray(holder, method);
@@ -686,11 +687,11 @@
     return resolveMethodOnClass(method, clazz);
   }
 
-  public ResolutionResult resolveMethodOnClass(DexMethod method, DexClass clazz) {
+  public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexClass clazz) {
     assert checkIfObsolete();
     assert !clazz.isInterface();
     // Step 2:
-    ResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+    MethodResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
     if (result != null) {
       return result;
     }
@@ -703,7 +704,7 @@
    * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
    * 5.4.3.3 of the JVM Spec</a>.
    */
-  private ResolutionResult resolveMethodOnClassStep2(
+  private MethodResolutionResult resolveMethodOnClassStep2(
       DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
     // Pt. 1: Signature polymorphic method check.
     // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
@@ -741,7 +742,7 @@
    * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
    * implementation.
    */
-  private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
+  private MethodResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
     MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
     resolveMethodStep3Helper(method, clazz, builder);
     return builder.resolve(clazz);
@@ -807,7 +808,7 @@
     return method != null && !method.accessFlags.isPrivate() && !method.accessFlags.isStatic();
   }
 
-  public ResolutionResult resolveMethodOnInterface(DexMethod method) {
+  public MethodResolutionResult resolveMethodOnInterface(DexMethod method) {
     return resolveMethodOnInterface(method.holder, method);
   }
 
@@ -822,7 +823,7 @@
    * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the
    * resolved method is used as basis for dispatch.
    */
-  public ResolutionResult resolveMethodOnInterface(DexType holder, DexMethod desc) {
+  public MethodResolutionResult resolveMethodOnInterface(DexType holder, DexMethod desc) {
     assert checkIfObsolete();
     if (holder.isArrayType()) {
       return IncompatibleClassResult.INSTANCE;
@@ -840,7 +841,7 @@
     return resolveMethodOnInterface(definition, desc);
   }
 
-  public ResolutionResult resolveMethodOnInterface(DexClass definition, DexMethod desc) {
+  public MethodResolutionResult resolveMethodOnInterface(DexClass definition, DexMethod desc) {
     assert checkIfObsolete();
     assert definition.isInterface();
     // Step 2: Look for exact method on interface.
@@ -1004,12 +1005,12 @@
           : null;
     }
 
-    ResolutionResult resolve(DexClass initialResolutionHolder) {
+    MethodResolutionResult resolve(DexClass initialResolutionHolder) {
       assert initialResolutionHolder != null;
       return internalResolve(initialResolutionHolder);
     }
 
-    private ResolutionResult internalResolve(DexClass initialResolutionHolder) {
+    private MethodResolutionResult internalResolve(DexClass initialResolutionHolder) {
       if (maximallySpecificMethods.isEmpty()) {
         return NoSuchMethodResult.INSTANCE;
       }
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 b509c1f..ebbcd93 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
 import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -84,6 +85,7 @@
   private final LibraryMethodSideEffectModelCollection libraryMethodSideEffectModelCollection;
 
   // Optimizations.
+  private final ArgumentPropagator argumentPropagator;
   private final CallSiteOptimizationInfoPropagator callSiteOptimizationInfoPropagator;
   private final LibraryMemberOptimizer libraryMemberOptimizer;
   private final ProtoShrinker protoShrinker;
@@ -121,9 +123,16 @@
     this.rewritePrefix = mapper;
 
     if (enableWholeProgramOptimizations() && options().callSiteOptimizationOptions().isEnabled()) {
-      this.callSiteOptimizationInfoPropagator =
-          new CallSiteOptimizationInfoPropagator(withLiveness());
+      if (options().callSiteOptimizationOptions().isExperimentalArgumentPropagationEnabled()) {
+        this.argumentPropagator = new ArgumentPropagator(withLiveness());
+        this.callSiteOptimizationInfoPropagator = null;
+      } else {
+        this.argumentPropagator = null;
+        this.callSiteOptimizationInfoPropagator =
+            new CallSiteOptimizationInfoPropagator(withLiveness());
+      }
     } else {
+      this.argumentPropagator = null;
       this.callSiteOptimizationInfoPropagator = null;
     }
 
@@ -323,6 +332,13 @@
     return callSiteOptimizationInfoPropagator;
   }
 
+  public <E extends Throwable> void withArgumentPropagator(
+      ThrowingConsumer<ArgumentPropagator, E> consumer) throws E {
+    if (argumentPropagator != null) {
+      consumer.accept(argumentPropagator);
+    }
+  }
+
   public <E extends Throwable> void withCallSiteOptimizationInfoPropagator(
       ThrowingConsumer<CallSiteOptimizationInfoPropagator, E> consumer) throws E {
     if (callSiteOptimizationInfoPropagator != null) {
@@ -480,6 +496,10 @@
     this.mainDexRootSet = mainDexRootSet;
   }
 
+  public boolean hasMainDexRootSet() {
+    return mainDexRootSet != null;
+  }
+
   public MainDexRootSet getMainDexRootSet() {
     return mainDexRootSet;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 49cab56..0213b0e 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -730,6 +730,33 @@
             thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end));
   }
 
+  @Override
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
+    Position callerPosition = Position.synthetic(0, caller, null);
+    List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2);
+    CfLabel firstLabel;
+    if (instructions.get(0).isLabel()) {
+      firstLabel = instructions.get(0).asLabel();
+    } else {
+      firstLabel = new CfLabel();
+      newInstructions.add(firstLabel);
+    }
+    newInstructions.add(new CfPosition(firstLabel, callerPosition));
+    for (CfInstruction instruction : instructions) {
+      if (instruction.isPosition()) {
+        CfPosition oldPosition = instruction.asPosition();
+        newInstructions.add(
+            new CfPosition(
+                oldPosition.getLabel(),
+                oldPosition.getPosition().withOutermostCallerPosition(callerPosition)));
+      } else {
+        newInstructions.add(instruction);
+      }
+    }
+    return new CfCode(
+        originalHolder, maxStack, maxLocals, newInstructions, tryCatchRanges, localVariables);
+  }
+
   public StackMapStatus verifyFrames(DexEncodedMethod method, AppView<?> appView, Origin origin) {
     return verifyFrames(method, appView, origin, RewrittenPrototypeDescription.none());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 0f6438f..83fc48c 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -103,4 +103,8 @@
   public boolean verifyNoInputReaders() {
     return true;
   }
+
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
+    throw new Unreachable();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/Definition.java b/src/main/java/com/android/tools/r8/graph/Definition.java
index b3dcc3a..00bca6f 100644
--- a/src/main/java/com/android/tools/r8/graph/Definition.java
+++ b/src/main/java/com/android/tools/r8/graph/Definition.java
@@ -26,6 +26,8 @@
 
   AccessFlags<?> getAccessFlags();
 
+  DexClass getContextClass();
+
   DexType getContextType();
 
   DexDefinition getDefinition();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index f3937c2..e4d00aa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -742,6 +742,11 @@
   }
 
   @Override
+  public DexClass getContextClass() {
+    return this;
+  }
+
+  @Override
   public DexClass getDefinition() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
index f82bcbc..703e841 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -27,6 +27,11 @@
   }
 
   @Override
+  public DexClass getContextClass() {
+    return getHolder();
+  }
+
+  @Override
   public DexType getContextType() {
     return getHolderType();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 378640d..589ae3a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
@@ -166,6 +167,52 @@
     return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
   }
 
+  @Override
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
+    return new DexCode(
+        registerSize,
+        incomingRegisterSize,
+        outgoingRegisterSize,
+        instructions,
+        tries,
+        handlers,
+        debugInfoAsInlining(caller, callee));
+  }
+
+  private DexDebugInfo debugInfoAsInlining(DexMethod caller, DexMethod callee) {
+    Position callerPosition = Position.synthetic(0, caller, null);
+    if (debugInfo == null) {
+      // If the method has no debug info we generate a preamble position to denote the inlining.
+      // This is consistent with the building IR for inlining which will always ensure the method
+      // has a position.
+      return new DexDebugInfo(
+          0,
+          new DexString[callee.getArity()],
+          new DexDebugEvent[] {
+            new DexDebugEvent.SetInlineFrame(callee, callerPosition),
+            DexDebugEvent.ZERO_CHANGE_DEFAULT_EVENT
+          });
+    }
+    DexDebugEvent[] oldEvents = debugInfo.events;
+    DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1];
+    int i = 0;
+    newEvents[i++] = new DexDebugEvent.SetInlineFrame(callee, callerPosition);
+    for (DexDebugEvent event : oldEvents) {
+      if (event instanceof SetInlineFrame) {
+        SetInlineFrame oldFrame = (SetInlineFrame) event;
+        newEvents[i++] =
+            new SetInlineFrame(
+                oldFrame.callee,
+                oldFrame.caller == null
+                    ? callerPosition
+                    : oldFrame.caller.withOutermostCallerPosition(callerPosition));
+      } else {
+        newEvents[i++] = event;
+      }
+    }
+    return new DexDebugInfo(debugInfo.startLine, debugInfo.parameters, newEvents);
+  }
+
   public static int getLargestPrefix(DexItemFactory factory, DexString name) {
     if (name != null && name.endsWith(factory.thisName)) {
       String string = name.toString();
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index b213660..1bbd88a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -23,6 +23,8 @@
 
   public static final DexDebugEvent[] EMPTY_ARRAY = {};
 
+  public static final DexDebugEvent.Default ZERO_CHANGE_DEFAULT_EVENT = Default.create(0, 0);
+
   public void collectIndexedItems(IndexedItemCollection collection, GraphLens graphLens) {
     // Empty by default.
   }
@@ -583,6 +585,16 @@
       this.value = value;
     }
 
+    public static int computeSpecialOpcode(int lineDelta, int pcDelta) {
+      return Constants.DBG_FIRST_SPECIAL
+          + (lineDelta - Constants.DBG_LINE_BASE)
+          + Constants.DBG_LINE_RANGE * pcDelta;
+    }
+
+    public static Default create(int lineDelta, int pcDelta) {
+      return new Default(computeSpecialOpcode(lineDelta, pcDelta));
+    }
+
     @Override
     public void writeOn(
         DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index bdfe991..d7c008c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.DebugLocalsChange;
@@ -253,23 +254,17 @@
       // TODO(herhut): To be super clever, encode only the part that is above limit.
       lineDelta = 0;
     }
-    int specialOpcode = computeSpecialOpcode(lineDelta, pcDelta);
+    int specialOpcode = Default.computeSpecialOpcode(lineDelta, pcDelta);
     if (specialOpcode > Constants.DBG_LAST_SPECIAL) {
       events.add(factory.createAdvancePC(pcDelta));
       // TODO(herhut): To be super clever, encode only the part that is above limit.
-      specialOpcode = computeSpecialOpcode(lineDelta, 0);
+      specialOpcode = Default.computeSpecialOpcode(lineDelta, 0);
     }
     assert specialOpcode >= Constants.DBG_FIRST_SPECIAL;
     assert specialOpcode <= Constants.DBG_LAST_SPECIAL;
     events.add(factory.createDefault(specialOpcode));
   }
 
-  private static int computeSpecialOpcode(int lineDelta, int pcDelta) {
-    return Constants.DBG_FIRST_SPECIAL
-        + (lineDelta - Constants.DBG_LINE_BASE)
-        + Constants.DBG_LINE_RANGE * pcDelta;
-  }
-
   private static void emitLocalChangeEvents(
       Int2ReferenceMap<DebugLocalInfo> previousLocals,
       Int2ReferenceMap<DebugLocalInfo> nextLocals,
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 c6913f8..cb44ff9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -199,13 +199,7 @@
   }
 
   public DexType getArgumentType(int argumentIndex) {
-    if (isStatic()) {
-      return getReference().getParameter(argumentIndex);
-    }
-    if (argumentIndex == 0) {
-      return getHolderType();
-    }
-    return getReference().getParameter(argumentIndex - 1);
+    return getReference().getArgumentType(argumentIndex, isStatic());
   }
 
   public int getNumberOfArguments() {
@@ -993,9 +987,15 @@
     assert !accessFlags.isFinal();
     // static abstract is an invalid access combination and we should never create that.
     assert !accessFlags.isStatic();
-    accessFlags.setAbstract();
-    this.code = null;
-    return this;
+    return builder(this)
+        .modifyAccessFlags(MethodAccessFlags::setAbstract)
+        .setIsLibraryMethodOverrideIf(
+            isNonPrivateVirtualMethod() && !isLibraryMethodOverride().isUnknown(),
+            isLibraryMethodOverride())
+        .unsetCode()
+        .addBuildConsumer(
+            method -> OptimizationFeedbackSimple.getInstance().unsetBridgeInfo(method))
+        .build();
   }
 
   /**
@@ -1585,7 +1585,7 @@
                       new ProgramMethod(holder, newMethod), simpleInliningConstraint));
     }
 
-    private Builder addBuildConsumer(Consumer<DexEncodedMethod> consumer) {
+    public Builder addBuildConsumer(Consumer<DexEncodedMethod> consumer) {
       this.buildConsumer = this.buildConsumer.andThen(consumer);
       return this;
     }
@@ -1721,6 +1721,10 @@
       return this;
     }
 
+    public Builder unsetCode() {
+      return setCode(null);
+    }
+
     public DexEncodedMethod build() {
       assert method != null;
       assert accessFlags != null;
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 e8f5829..0100fd5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
@@ -717,6 +718,44 @@
           .put(doubleType, boxedDoubleType)
           .build());
 
+  public final Map<DexType, DexMethod> unboxPrimitiveMethod =
+      ImmutableMap.<DexType, DexMethod>builder()
+          .put(boxedBooleanType, createUnboxMethod(booleanType, unboxBooleanMethodName))
+          .put(boxedByteType, createUnboxMethod(byteType, unboxByteMethodName))
+          .put(boxedCharType, createUnboxMethod(charType, unboxCharMethodName))
+          .put(boxedShortType, createUnboxMethod(shortType, unboxShortMethodName))
+          .put(boxedIntType, createUnboxMethod(intType, unboxIntMethodName))
+          .put(boxedLongType, createUnboxMethod(longType, unboxLongMethodName))
+          .put(boxedFloatType, createUnboxMethod(floatType, unboxFloatMethodName))
+          .put(boxedDoubleType, createUnboxMethod(doubleType, unboxDoubleMethodName))
+          .build();
+
+  private DexMethod createUnboxMethod(DexType primitiveType, DexString unboxMethodName) {
+    DexProto proto = createProto(primitiveType);
+    return createMethod(primitiveToBoxed.get(primitiveType), proto, unboxMethodName);
+  }
+
+  // Works both with the boxed and unboxed type.
+  public DexMethod getUnboxPrimitiveMethod(DexType type) {
+    DexType boxType = primitiveToBoxed.getOrDefault(type, type);
+    DexMethod unboxMethod = unboxPrimitiveMethod.get(boxType);
+    if (unboxMethod == null) {
+      throw new Unreachable("Invalid primitive type descriptor: " + type);
+    }
+    return unboxMethod;
+  }
+
+  // Works both with the boxed and unboxed type.
+  public DexMethod getBoxPrimitiveMethod(DexType type) {
+    DexType boxType = primitiveToBoxed.getOrDefault(type, type);
+    DexType primitive = getPrimitiveFromBoxed(boxType);
+    if (primitive == null) {
+      throw new Unreachable("Invalid primitive type descriptor: " + type);
+    }
+    DexProto proto = createProto(boxType, primitive);
+    return createMethod(boxType, proto, valueOfMethodName);
+  }
+
   public DexType getBoxedForPrimitiveType(DexType primitive) {
     assert primitive.isPrimitiveType();
     return primitiveToBoxed.get(primitive);
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 15ac70a..5a109e7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -59,6 +59,16 @@
     return visitor.visitDexMethod(this, other);
   }
 
+  public DexType getArgumentType(int argumentIndex, boolean isStatic) {
+    if (isStatic) {
+      return getParameter(argumentIndex);
+    }
+    if (argumentIndex == 0) {
+      return getHolderType();
+    }
+    return getParameter(argumentIndex - 1);
+  }
+
   public DexType getParameter(int index) {
     return proto.getParameter(index);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 96c3084..d276e9b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -7,6 +7,8 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -52,6 +54,10 @@
     return Reference.classFromDescriptor(toDescriptorString());
   }
 
+  public TypeElement toTypeElement(AppView<?> appView) {
+    return TypeElement.fromDexType(this, Nullability.maybeNull(), appView);
+  }
+
   @Override
   public int compareTo(DexReference other) {
     if (other.isDexType()) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index 554cadc..62b62b9 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -17,6 +17,16 @@
     return UnknownFieldResolutionResult.INSTANCE;
   }
 
+  @Override
+  public boolean isFieldResolutionResult() {
+    return true;
+  }
+
+  @Override
+  public FieldResolutionResult asFieldResolutionResult() {
+    return this;
+  }
+
   public DexEncodedField getResolvedField() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
index d8c3142..8fa722d 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.List;
 import java.util.function.Consumer;
@@ -84,6 +85,7 @@
 
   private final AppView<?> appView;
   private final Mode mode;
+  private final InternalOptions options;
   private final GenericSignatureContextBuilder contextBuilder;
 
   private GenericSignatureCorrectnessHelper(
@@ -91,6 +93,7 @@
     this.appView = appView;
     this.contextBuilder = contextBuilder;
     this.mode = mode;
+    this.options = appView.options();
   }
 
   public static GenericSignatureCorrectnessHelper createForInitialCheck(
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 8e436ba..8da7519 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.optimize.MemberRebindingLens;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
@@ -505,11 +506,12 @@
     return true;
   }
 
-  public <T extends DexReference> boolean assertPinnedNotModified(KeepInfoCollection keepInfo) {
+  public <T extends DexReference> boolean assertPinnedNotModified(
+      KeepInfoCollection keepInfo, InternalOptions options) {
     List<DexReference> pinnedItems = new ArrayList<>();
-    keepInfo.forEachPinnedType(pinnedItems::add);
-    keepInfo.forEachPinnedMethod(pinnedItems::add);
-    keepInfo.forEachPinnedField(pinnedItems::add);
+    keepInfo.forEachPinnedType(pinnedItems::add, options);
+    keepInfo.forEachPinnedMethod(pinnedItems::add, options);
+    keepInfo.forEachPinnedField(pinnedItems::add, options);
     return assertReferencesNotModified(pinnedItems);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
new file mode 100644
index 0000000..ac08e3f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -0,0 +1,87 @@
+// 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.graph;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public class ImmediateProgramSubtypingInfo {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final Map<DexProgramClass, List<DexProgramClass>> immediateSubtypes;
+
+  private ImmediateProgramSubtypingInfo(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Map<DexProgramClass, List<DexProgramClass>> immediateSubtypes) {
+    this.appView = appView;
+    this.immediateSubtypes = immediateSubtypes;
+  }
+
+  public static ImmediateProgramSubtypingInfo create(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    Map<DexProgramClass, List<DexProgramClass>> immediateSubtypes = new IdentityHashMap<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachImmediateSupertype(
+          supertype -> {
+            DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(supertype));
+            if (superclass != null) {
+              immediateSubtypes.computeIfAbsent(superclass, ignoreKey(ArrayList::new)).add(clazz);
+            }
+          });
+    }
+    return new ImmediateProgramSubtypingInfo(appView, immediateSubtypes);
+  }
+
+  public void forEachImmediateSuperClass(
+      DexProgramClass clazz, BiConsumer<? super DexType, ? super DexClass> consumer) {
+    forEachImmediateSuperClassMatching(clazz, (supertype, superclass) -> true, consumer);
+  }
+
+  public void forEachImmediateSuperClassMatching(
+      DexProgramClass clazz,
+      BiPredicate<? super DexType, ? super DexClass> predicate,
+      BiConsumer<? super DexType, ? super DexClass> consumer) {
+    clazz.forEachImmediateSupertype(
+        supertype -> {
+          DexClass superclass = appView.definitionFor(supertype);
+          if (predicate.test(supertype, superclass)) {
+            consumer.accept(supertype, superclass);
+          }
+        });
+  }
+
+  public void forEachImmediateSubClass(
+      DexProgramClass clazz, Consumer<? super DexProgramClass> consumer) {
+    forEachImmediateSubClassMatching(clazz, alwaysTrue(), consumer);
+  }
+
+  public void forEachImmediateSubClassMatching(
+      DexProgramClass clazz,
+      Predicate<? super DexProgramClass> predicate,
+      Consumer<? super DexProgramClass> consumer) {
+    getSubclasses(clazz)
+        .forEach(
+            subclass -> {
+              if (predicate.test(subclass)) {
+                consumer.accept(subclass);
+              }
+            });
+  }
+
+  public List<DexProgramClass> getSubclasses(DexProgramClass clazz) {
+    return immediateSubtypes.getOrDefault(clazz, Collections.emptyList());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index fbefa45..a520ae3 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -150,6 +150,11 @@
     return code;
   }
 
+  @Override
+  public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
+    return asCfCode().getCodeAsInlining(caller, callee);
+  }
+
   public static class DebugParsingOptions {
     public final boolean lineInfo;
     public final boolean localInfo;
diff --git a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
index 285859d..c3f3c42 100644
--- a/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MemberResolutionResult.java
@@ -24,4 +24,20 @@
       ProgramDefinition context, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return isAccessibleFrom(context, appView.appInfo());
   }
+
+  public boolean isFieldResolutionResult() {
+    return false;
+  }
+
+  public boolean isMethodResolutionResult() {
+    return false;
+  }
+
+  public FieldResolutionResult asFieldResolutionResult() {
+    return null;
+  }
+
+  public MethodResolutionResult asMethodResolutionResult() {
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
similarity index 98%
rename from src/main/java/com/android/tools/r8/graph/ResolutionResult.java
rename to src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index e3558a2..aadd446 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -19,7 +19,18 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
-public abstract class ResolutionResult extends MemberResolutionResult<DexEncodedMethod, DexMethod> {
+public abstract class MethodResolutionResult
+    extends MemberResolutionResult<DexEncodedMethod, DexMethod> {
+
+  @Override
+  public boolean isMethodResolutionResult() {
+    return true;
+  }
+
+  @Override
+  public MethodResolutionResult asMethodResolutionResult() {
+    return this;
+  }
 
   /**
    * Returns true if resolution succeeded *and* the resolved method has a known definition.
@@ -137,7 +148,7 @@
       LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
 
   /** Result for a resolution that succeeds with a known declaration/definition. */
-  public static class SingleResolutionResult extends ResolutionResult
+  public static class SingleResolutionResult extends MethodResolutionResult
       implements SuccessfulMemberResolutionResult<DexEncodedMethod, DexMethod> {
     private final DexClass initialResolutionHolder;
     private final DexClass resolvedHolder;
@@ -700,7 +711,7 @@
     }
   }
 
-  abstract static class EmptyResult extends ResolutionResult {
+  abstract static class EmptyResult extends MethodResolutionResult {
 
     @Override
     public final DexClassAndMethod lookupInvokeSpecialTarget(
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 946bdbc..7f09192 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -39,6 +39,9 @@
   /** Instantiated classes without contexts. */
   final Set<DexProgramClass> classesWithoutAllocationSiteTracking = Sets.newIdentityHashSet();
 
+  /** Set of annotation types for which the subtype hierarchy is unknown from that type. */
+  final Set<DexProgramClass> annotationsWithUnknownSubtypeHierarchy = Sets.newIdentityHashSet();
+
   /**
    * Set of interface types for which the subtype hierarchy is unknown from that type.
    *
@@ -103,7 +106,8 @@
     if (!clazz.isInterface()) {
       return false;
     }
-    return interfacesWithUnknownSubtypeHierarchy.contains(clazz)
+    return annotationsWithUnknownSubtypeHierarchy.contains(clazz)
+        || interfacesWithUnknownSubtypeHierarchy.contains(clazz)
         || isImmediateInterfaceOfInstantiatedLambda(clazz);
   }
 
@@ -227,6 +231,8 @@
         .removeIf(entry -> removedClasses.contains(entry.getKey().getType()));
     classesWithoutAllocationSiteTracking.removeIf(
         clazz -> removedClasses.contains(clazz.getType()));
+    annotationsWithUnknownSubtypeHierarchy.removeIf(
+        annotation -> removedClasses.contains(annotation.getType()));
     boolean removed =
         interfacesWithUnknownSubtypeHierarchy.removeIf(
             iface -> removedClasses.contains(iface.getType()));
@@ -242,6 +248,9 @@
     for (DexProgramClass clazz : classesWithoutAllocationSiteTracking) {
       assert liveTypes.contains(clazz.getType());
     }
+    for (DexProgramClass annotation : annotationsWithUnknownSubtypeHierarchy) {
+      assert liveTypes.contains(annotation.getType());
+    }
     for (DexProgramClass iface : interfacesWithUnknownSubtypeHierarchy) {
       assert liveTypes.contains(iface.getType());
     }
@@ -338,6 +347,16 @@
       return false;
     }
 
+    public boolean recordInstantiatedAnnotation(DexProgramClass annotation, AppInfo appInfo) {
+      assert annotation.isInterface();
+      assert annotation.isAnnotation();
+      if (annotationsWithUnknownSubtypeHierarchy.add(annotation)) {
+        populateInstantiatedHierarchy(appInfo, annotation);
+        return true;
+      }
+      return false;
+    }
+
     public boolean recordInstantiatedInterface(DexProgramClass iface, AppInfo appInfo) {
       assert iface.isInterface();
       assert !iface.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index b36e87c..ab0ee34 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -42,6 +42,7 @@
     getDefinition().rewriteAllAnnotations(rewriter);
   }
 
+  @Override
   DexProgramClass getContextClass();
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index d53cb1c..0007521 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -33,6 +33,11 @@
   }
 
   @Override
+  public DexProgramClass getContextClass() {
+    return getHolder();
+  }
+
+  @Override
   public boolean isProgramField() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index a34b49d..5d5a755 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -68,6 +68,11 @@
   }
 
   @Override
+  public DexProgramClass getContextClass() {
+    return getHolder();
+  }
+
+  @Override
   public boolean isProgramMember() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 969ac7d..d09c3f5 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
@@ -202,13 +203,30 @@
       DexType newInnerClassType = fixupTypeOrNull(innerClassType);
       DexType outerClassType = innerClassAttribute.getOuter();
       DexType newOuterClassType = fixupTypeOrNull(outerClassType);
+      DexString newInnerName = innerClassAttribute.getInnerName();
+      // Compute the new inner name if the attribute changed. This could end up 'fixing' invalid
+      // inner class attributes.
+      boolean innerClassAttributeChanged =
+          newInnerClassType != innerClassType || newOuterClassType != outerClassType;
+      if (innerClassAttributeChanged && innerClassType != null && outerClassType != null) {
+        String innerClassName =
+            DescriptorUtils.getInnerClassName(
+                newOuterClassType.toDescriptorString(), newInnerClassType.toDescriptorString());
+        if (innerClassName != null) {
+          newInnerName = dexItemFactory.createString(innerClassName);
+        } else {
+          // If run without treeshaking and the outer type is missing we are not pruning the
+          // relationship.
+          // TODO(b/196503304): Enable the below asserts when resolved.
+          assert !appView.options().isTreeShakingEnabled() || true;
+          // assert appView.appInfo().definitionForWithoutExistenceAssert(newOuterClassType) ==
+          // null;
+        }
+      }
       newInnerClassAttributes.add(
           new InnerClassAttribute(
-              innerClassAttribute.getAccess(),
-              newInnerClassType,
-              newOuterClassType,
-              innerClassAttribute.getInnerName()));
-      changed |= newInnerClassType != innerClassType || newOuterClassType != outerClassType;
+              innerClassAttribute.getAccess(), newInnerClassType, newOuterClassType, newInnerName));
+      changed |= innerClassAttributeChanged;
     }
     return changed ? newInnerClassAttributes : innerClassAttributes;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
deleted file mode 100644
index 28d6be7..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph.analysis;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClasspathClass;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.function.Consumer;
-
-public class DesugaredLibraryConversionWrapperAnalysis extends EnqueuerAnalysis
-    implements EnqueuerInvokeAnalysis {
-
-  private final AppView<?> appView;
-  private final DesugaredLibraryAPIConverter converter;
-
-  public DesugaredLibraryConversionWrapperAnalysis(AppView<?> appView) {
-    this.appView = appView;
-    this.converter =
-        new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
-  }
-
-  @Override
-  public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
-    converter.registerCallbackIfRequired(method);
-  }
-
-  private void traceInvoke(DexMethod invokedMethod) {
-    converter.registerWrappersForLibraryInvokeIfRequired(invokedMethod);
-  }
-
-  @Override
-  public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
-  }
-
-  @Override
-  public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
-  }
-
-  @Override
-  public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
-  }
-
-  @Override
-  public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
-  }
-
-  @Override
-  public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {
-    this.traceInvoke(invokedMethod);
-  }
-
-  public ProgramMethodSet generateCallbackMethods() {
-    return converter.generateCallbackMethods();
-  }
-
-  public void generateWrappers(Consumer<DexClasspathClass> synthesizedCallback) {
-    converter.synthesizeWrappers(synthesizedCallback);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index d5f2b8b..87a3211 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -17,9 +17,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.horizontalclassmerging.code.VirtualMethodEntryPointSynthesizedCode;
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.utils.ListUtils;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
index 82b76d9..63461b5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
 import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Set;
@@ -19,20 +20,22 @@
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final KeepInfoCollection keepInfo;
+  private final InternalOptions options;
 
   private final Set<DexType> dontMergeTypes = Sets.newIdentityHashSet();
 
   public NoKeepRules(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     this.keepInfo = appView.getKeepInfo();
+    this.options = appView.options();
     appView.appInfo().classes().forEach(this::processClass);
   }
 
   private void processClass(DexProgramClass clazz) {
     DexType type = clazz.getType();
-    boolean pinHolder = keepInfo.getClassInfo(clazz).isPinned();
+    boolean pinHolder = keepInfo.getClassInfo(clazz).isPinned(options);
     for (DexEncodedMember<?, ?> member : clazz.members()) {
-      if (keepInfo.getMemberInfo(member, clazz).isPinned()) {
+      if (keepInfo.getMemberInfo(member, clazz).isPinned(options)) {
         pinHolder = true;
         Iterables.addAll(
             dontMergeTypes,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
index 79b2bb2..54868a8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 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.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index 9ec219c..7c53bc3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
 import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index dbaf662..a46b2e0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -13,8 +13,8 @@
 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.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
@@ -332,7 +332,7 @@
         }
       }
       DexMethod method = instruction.getInvokedMethod();
-      ResolutionResult resolutionResult =
+      MethodResolutionResult resolutionResult =
           appView.appInfo().resolveMethodOnInterface(method.holder, method);
       if (!resolutionResult.isSingleResolution()) {
         return false;
@@ -393,7 +393,7 @@
       if (superType == null) {
         return false;
       }
-      ResolutionResult resolutionResult =
+      MethodResolutionResult resolutionResult =
           appView.appInfo().resolveMethodOn(superType, method, instruction.isInterface);
       if (!resolutionResult.isSingleResolution()) {
         return false;
@@ -430,7 +430,7 @@
         }
       }
       DexMethod method = instruction.getInvokedMethod();
-      ResolutionResult resolutionResult =
+      MethodResolutionResult resolutionResult =
           appView.appInfo().resolveMethodOnClass(method, method.holder);
       if (!resolutionResult.isSingleResolution()) {
         return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index d870dc7..9af0407 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
@@ -81,11 +82,13 @@
   private final AppView<?> appView;
   private final ProgramMethod context;
   private final DexItemFactory dexItemFactory;
+  private final InternalOptions options;
 
   public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.context = code.context();
     this.dexItemFactory = appView.dexItemFactory();
+    this.options = appView.options();
   }
 
   public boolean anyValueMayDependOnEnvironment(Iterable<Value> values) {
@@ -270,7 +273,9 @@
   private boolean isNonPinnedClassConstant(Value value) {
     Value root = value.getAliasedValue();
     return root.isDefinedByInstructionSatisfying(Instruction::isConstClass)
-        && !appView.getKeepInfo().isPinned(root.getDefinition().asConstClass().getType(), appView);
+        && !appView
+            .getKeepInfo()
+            .isPinned(root.getDefinition().asConstClass().getType(), appView, options);
   }
 
   private boolean addLogicalBinopValueToValueGraph(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index d1b411a..70c8519 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -191,7 +191,7 @@
               abstractValue.join(
                   argumentAbstractValue,
                   appView.abstractValueFactory(),
-                  field.getType(),
+                  field.getType().isReferenceType(),
                   isClassIdField);
           assert !abstractValue.isBottom();
         } else if (initializationInfo.isSingleValue()) {
@@ -200,7 +200,7 @@
               abstractValue.join(
                   singleValueInitializationInfo,
                   appView.abstractValueFactory(),
-                  field.getType(),
+                  field.getType().isReferenceType(),
                   isClassIdField);
         } else if (initializationInfo.isTypeInitializationInfo()) {
           // TODO(b/149732532): Not handled, for now.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index be5a107..01c9fe0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.shaking.Enqueuer.Mode;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Sets;
@@ -75,6 +76,7 @@
 public class GeneratedExtensionRegistryShrinker {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final InternalOptions options;
   private final ProtoReferences references;
 
   private final Map<DexType, Map<DexField, Mode>> removedExtensionFields = new IdentityHashMap<>();
@@ -83,6 +85,7 @@
       AppView<AppInfoWithLiveness> appView, ProtoReferences references) {
     assert appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking;
     this.appView = appView;
+    this.options = appView.options();
     this.references = references;
   }
 
@@ -244,7 +247,7 @@
       ProgramField field,
       FieldAccessInfoCollection<?> fieldAccessInfoCollection,
       KeepInfoCollection keepInfo) {
-    if (keepInfo.getFieldInfo(field).isPinned()) {
+    if (keepInfo.getFieldInfo(field).isPinned(options)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
new file mode 100644
index 0000000..12d1c4c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
@@ -0,0 +1,83 @@
+// 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.analysis.type;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+/**
+ * Represents the runtime type of a reference value. This type may be more precise than the value's
+ * statically declared type.
+ *
+ * <p>If a lower bound is known on the runtime type (e.g., {@code new A()}), then {@link
+ * DynamicTypeWithLowerBound} is used.
+ */
+public class DynamicType {
+
+  private static final DynamicType BOTTOM = new DynamicType(TypeElement.getBottom());
+  private static final DynamicType UNKNOWN = new DynamicType(TypeElement.getTop());
+
+  private final TypeElement dynamicUpperBoundType;
+
+  DynamicType(TypeElement dynamicUpperBoundType) {
+    assert dynamicUpperBoundType != null;
+    this.dynamicUpperBoundType = dynamicUpperBoundType;
+  }
+
+  public static DynamicType create(Value value, AppView<AppInfoWithLiveness> appView) {
+    assert value.getType().isReferenceType();
+    TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+    ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+    if (dynamicLowerBoundType != null) {
+      assert dynamicUpperBoundType.isClassType();
+      return DynamicTypeWithLowerBound.create(
+          appView, dynamicUpperBoundType.asClassType(), dynamicLowerBoundType);
+    }
+    return new DynamicType(dynamicUpperBoundType);
+  }
+
+  public static DynamicType bottom() {
+    return BOTTOM;
+  }
+
+  public static DynamicType unknown() {
+    return UNKNOWN;
+  }
+
+  public TypeElement getDynamicUpperBoundType() {
+    return dynamicUpperBoundType;
+  }
+
+  public boolean hasDynamicLowerBoundType() {
+    return false;
+  }
+
+  public ClassTypeElement getDynamicLowerBoundType() {
+    return null;
+  }
+
+  public boolean isTrivial(TypeElement staticType) {
+    return staticType == getDynamicUpperBoundType() || isUnknown();
+  }
+
+  public boolean isUnknown() {
+    return getDynamicUpperBoundType().isTop();
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    DynamicType assumption = (DynamicType) other;
+    return dynamicUpperBoundType == assumption.dynamicUpperBoundType;
+  }
+
+  @Override
+  public int hashCode() {
+    return dynamicUpperBoundType.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
new file mode 100644
index 0000000..ae61ee3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
@@ -0,0 +1,60 @@
+// 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.analysis.type;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Objects;
+
+public class DynamicTypeWithLowerBound extends DynamicType {
+
+  private final ClassTypeElement dynamicLowerBoundType;
+
+  DynamicTypeWithLowerBound(
+      ClassTypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
+    super(dynamicUpperBoundType);
+    this.dynamicLowerBoundType = dynamicLowerBoundType;
+  }
+
+  public static DynamicTypeWithLowerBound create(
+      AppView<AppInfoWithLiveness> appView,
+      ClassTypeElement dynamicUpperBoundType,
+      ClassTypeElement dynamicLowerBoundType) {
+    assert appView
+        .appInfo()
+        .isSubtype(dynamicLowerBoundType.getClassType(), dynamicUpperBoundType.getClassType());
+    return new DynamicTypeWithLowerBound(dynamicUpperBoundType, dynamicLowerBoundType);
+  }
+
+  @Override
+  public boolean hasDynamicLowerBoundType() {
+    return true;
+  }
+
+  @Override
+  public ClassTypeElement getDynamicLowerBoundType() {
+    return dynamicLowerBoundType;
+  }
+
+  @Override
+  public boolean isTrivial(TypeElement staticType) {
+    return false;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    DynamicTypeWithLowerBound assumption = (DynamicTypeWithLowerBound) other;
+    return getDynamicUpperBoundType() == assumption.getDynamicUpperBoundType()
+        && getDynamicLowerBoundType() == assumption.getDynamicLowerBoundType();
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getDynamicUpperBoundType(), getDynamicLowerBoundType());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index a512bd4..750169d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -11,6 +11,14 @@
 
 public abstract class AbstractValue {
 
+  public static BottomValue bottom() {
+    return BottomValue.getInstance();
+  }
+
+  public static UnknownValue unknown() {
+    return UnknownValue.getInstance();
+  }
+
   public abstract boolean isNonTrivial();
 
   public boolean isSingleBoolean() {
@@ -142,13 +150,15 @@
   }
 
   public AbstractValue join(AbstractValue other, AbstractValueFactory factory, DexType type) {
-    return join(other, factory, type, false);
+    return join(other, factory, type.isReferenceType(), false);
   }
 
+  // TODO(b/196321452): Clean this up, in particular, replace the "allow" parameters by a
+  //  configuration object.
   public AbstractValue join(
       AbstractValue other,
       AbstractValueFactory factory,
-      DexType type,
+      boolean allowNullOrAbstractValue,
       boolean allowNonConstantNumbers) {
     if (isBottom() || other.isUnknown()) {
       return other;
@@ -159,7 +169,7 @@
     if (equals(other)) {
       return this;
     }
-    if (type.isReferenceType()) {
+    if (allowNullOrAbstractValue) {
       if (isNull()) {
         return NullOrAbstractValue.create(other);
       }
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 0d9d21a..6c22403 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
@@ -15,8 +15,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
@@ -115,7 +115,8 @@
         refinedReceiverLowerBound = null;
       }
     }
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, getInterfaceBit());
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethod(method, getInterfaceBit());
     LookupResult lookupResult;
     if (refinedReceiverUpperBound != null) {
       lookupResult =
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 6a83961..38e86e1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -10,8 +10,8 @@
 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.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 9313b59..f1f86b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -13,8 +13,8 @@
 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.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 50899ca..05fba04 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -13,8 +13,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.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -184,7 +184,7 @@
     }
 
     // Verify that the object does not have a finalizer.
-    ResolutionResult finalizeResolutionResult =
+    MethodResolutionResult finalizeResolutionResult =
         appViewWithLiveness
             .appInfo()
             .resolveMethodOnClass(dexItemFactory.objectMembers.finalize, clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index d582fdb..ce8efd1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -22,6 +22,7 @@
 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.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -1083,6 +1084,10 @@
     return type;
   }
 
+  public DynamicType getDynamicType(AppView<AppInfoWithLiveness> appView) {
+    return DynamicType.create(this, appView);
+  }
+
   public TypeElement getDynamicUpperBoundType(
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     Value root = getAliasedValue();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 48656ec..1e5a344 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -19,9 +19,9 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
@@ -173,7 +173,7 @@
       Invoke.Type type = result.getType();
       if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
         // For virtual and interface calls add all potential targets that could be called.
-        ResolutionResult resolutionResult =
+        MethodResolutionResult resolutionResult =
             appView.appInfo().resolveMethod(method, type == Invoke.Type.INTERFACE);
         DexEncodedMethod target = resolutionResult.getSingleTarget();
         if (target != null) {
@@ -215,7 +215,8 @@
           possibleProgramTargetsCache.computeIfAbsent(
               target,
               method -> {
-                ResolutionResult resolution = appView.appInfo().resolveMethod(method, isInterface);
+                MethodResolutionResult resolution =
+                    appView.appInfo().resolveMethod(method, isInterface);
                 if (resolution.isVirtualTarget()) {
                   LookupResult lookupResult =
                       resolution.lookupVirtualDispatchTargets(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 5a492f7..0085b70 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -199,6 +199,7 @@
         Instruction throwing = instructions.removeLast();
         assert throwing.isThrow();
         UninitializedThisLocalRead read = new UninitializedThisLocalRead(code.getThis());
+        read.setPosition(throwing.getPosition());
         uninitializedThisLocalReads.add(read);
         read.setBlock(exitBlock);
         instructions.addLast(read);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 136d452..3129b82 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -76,6 +76,11 @@
       D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer =
           CfInstructionDesugaringEventConsumer.createForD8(methodProcessor);
 
+      // TODO(b/191656218): Move upfront the loop and use maybe the class event consumer.
+      if (appView.options().isDesugaredLibraryCompilation()) {
+        converter.ensureWrappersForL8(instructionDesugaringEventConsumer);
+      }
+
       // Process the wave and wait for all IR processing to complete.
       methodProcessor.newWave();
       ThreadUtils.processItems(
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 2563fa8..3911614 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
@@ -117,6 +117,7 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorIROptimizer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Pair;
@@ -734,9 +735,23 @@
     // will be passed during (double) inlining. Instead of adding assumptions and removing invalid
     // ones, it's better not to insert assumptions for inlinee in the beginning.
     CallSiteOptimizationInfo callSiteOptimizationInfo = getMethod().getCallSiteOptimizationInfo();
-    if (method == context && appView.callSiteOptimizationInfoPropagator() != null) {
-      appView.callSiteOptimizationInfoPropagator()
-          .applyCallSiteOptimizationInfo(ir, callSiteOptimizationInfo);
+    if (callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo() && method == context) {
+      // TODO(b/190154391): Consider pruning all argument information from the optimization info
+      //  after the second optimization pass. That way we save memory and can assert here that
+      //  !appView.hasLiveness() (which currently may happen due to the reflective behavior
+      //  handling in the final round of tree shaking).
+      if (appView.hasLiveness()) {
+        if (appView
+                .options()
+                .callSiteOptimizationOptions()
+                .isExperimentalArgumentPropagationEnabled()
+            || appView.callSiteOptimizationInfoPropagator().getMode().isRevisit()) {
+          ArgumentPropagatorIROptimizer.optimize(
+              appView.withLiveness(),
+              ir,
+              callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo());
+        }
+      }
     }
 
     if (appView.options().isStringSwitchConversionEnabled()) {
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 3144113..73ef0f1 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
@@ -55,7 +55,6 @@
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
@@ -96,9 +95,9 @@
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
+import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
@@ -156,7 +155,6 @@
   private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
   private final StringSwitchRemover stringSwitchRemover;
   private final TypeChecker typeChecker;
-  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
   private final ServiceLoaderRewriter serviceLoaderRewriter;
   private final EnumValueOptimizer enumValueOptimizer;
   private final EnumUnboxer enumUnboxer;
@@ -226,10 +224,8 @@
       // - invoke-special desugaring.
       assert options.desugarState.isOn();
       this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
-      this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
+      this.classDesugaring = CfClassDesugaringCollection.create(appView);
       this.interfaceMethodRewriter = null;
-      this.desugaredLibraryAPIConverter =
-          new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
       this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
@@ -255,7 +251,10 @@
         appView.enableWholeProgramOptimizations()
             ? CfInstructionDesugaringCollection.empty()
             : CfInstructionDesugaringCollection.create(appView);
-    this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
+    this.classDesugaring =
+        appView.enableWholeProgramOptimizations()
+            ? CfClassDesugaringCollection.empty()
+            : CfClassDesugaringCollection.create(appView);
     this.interfaceMethodRewriter =
         options.isInterfaceMethodDesugaringEnabled() && appView.enableWholeProgramOptimizations()
             ? new InterfaceMethodRewriter(appView, this)
@@ -300,11 +299,6 @@
           options.enableServiceLoaderRewriting
               ? new ServiceLoaderRewriter(appViewWithLiveness)
               : null;
-      this.desugaredLibraryAPIConverter =
-          appView.rewritePrefix.isRewriting()
-              ? new DesugaredLibraryAPIConverter(
-                  appView, Mode.ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED)
-              : null;
       this.enumValueOptimizer =
           options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
     } else {
@@ -321,10 +315,6 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
       this.typeChecker = null;
-      this.desugaredLibraryAPIConverter =
-          appView.rewritePrefix.isRewriting()
-              ? new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS)
-              : null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
       this.enumValueOptimizer = null;
@@ -365,6 +355,13 @@
         D8NestBasedAccessDesugaring::clearNestAttributes);
   }
 
+  public void ensureWrappersForL8(
+      D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    instructionDesugaring.withDesugaredLibraryAPIConverter(
+        converter -> converter.ensureWrappersForL8(instructionDesugaringEventConsumer));
+  }
+
   private void staticizeClasses(
       OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
       throws ExecutionException {
@@ -430,7 +427,6 @@
       new EmulatedInterfaceApplicationRewriter(appView).rewriteApplication(builder);
     }
     processCovariantReturnTypeAnnotations(builder);
-    generateDesugaredLibraryAPIWrappers(builder, executor);
 
     timing.end();
 
@@ -457,13 +453,14 @@
       D8MethodProcessor methodProcessor, ExecutorService executorService)
       throws ExecutionException {
     D8CfPostProcessingDesugaringEventConsumer eventConsumer =
-        CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor, appView);
+        CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor);
     methodProcessor.newWave();
     InterfaceMethodProcessorFacade interfaceDesugaring =
         instructionDesugaring.getInterfaceMethodPostProcessingDesugaring(ExcludeDexResources);
     CfPostProcessingDesugaringCollection.create(
             appView, interfaceDesugaring, instructionDesugaring.getRetargetingInfo())
-        .postProcessingDesugaring(eventConsumer, executorService);
+        .postProcessingDesugaring(appView.appInfo().classes(), eventConsumer, executorService);
+    methodProcessor.awaitMethodProcessing();
     eventConsumer.finalizeDesugaring();
   }
 
@@ -481,6 +478,9 @@
 
     rewriteEnclosingLambdaMethodAttributes(
         appView, classConverterResult.getForcefullyMovedLambdaMethods());
+
+    instructionDesugaring.withDesugaredLibraryAPIConverter(
+        DesugaredLibraryAPIConverter::generateTrackingWarnings);
   }
 
   public void desugarClassesForD8(
@@ -589,32 +589,14 @@
     }
   }
 
-  private boolean needsIRConversion(ProgramMethod method) {
+  private boolean needsIRConversion() {
     if (appView.enableWholeProgramOptimizations()) {
       return true;
     }
     if (options.testing.forceIRForCfToCfDesugar) {
       return true;
     }
-    if (options.isDesugaredLibraryCompilation()) {
-      return true;
-    }
-    if (!options.cfToCfDesugar) {
-      return true;
-    }
-    if (desugaredLibraryAPIConverter != null
-        && desugaredLibraryAPIConverter.shouldRegisterCallback(method)) {
-      return true;
-    }
-    if (method.getDefinition().getCode() instanceof SynthesizedCode) {
-      // SynthesizedCode needs IR to generate the code.
-      return true;
-    } else {
-      NeedsIRDesugarUseRegistry useRegistry =
-          new NeedsIRDesugarUseRegistry(method, appView, desugaredLibraryAPIConverter);
-      method.registerCodeReferences(useRegistry);
-      return useRegistry.needsDesugaring();
-    }
+    return !options.cfToCfDesugar;
   }
 
   private void checkPrefixMerging(ProgramMethod method) {
@@ -691,6 +673,8 @@
 
     printPhase("Primary optimization pass");
 
+    // Setup the argument propagator for the primary optimization pass.
+    appView.withArgumentPropagator(ArgumentPropagator::initializeCodeScanner);
     appView.withCallSiteOptimizationInfoPropagator(
         optimization -> {
           optimization.abandonCallSitePropagationForLambdaImplementationMethods(
@@ -736,11 +720,21 @@
     // Assure that no more optimization feedback left after primary processing.
     assert feedback.noUpdatesLeft();
     appView.setAllCodeProcessed();
+
     // All the code has been processed so the rewriting required by the lenses is done everywhere,
     // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
     // lenses with code rewriting are added.
     appView.clearCodeRewritings();
 
+    // Analyze the data collected by the argument propagator, use the analysis result to update
+    // the parameter optimization infos, and rewrite the application.
+    appView.withArgumentPropagator(
+        argumentPropagator -> {
+          argumentPropagator.populateParameterOptimizationInfo(executorService);
+          argumentPropagator.optimizeMethodParameters();
+          argumentPropagator.enqueueMethodsForProcessing(postMethodProcessorBuilder);
+        });
+
     if (libraryMethodOverrideAnalysis != null) {
       libraryMethodOverrideAnalysis.finish();
     }
@@ -818,9 +812,6 @@
     runInterfaceDesugaringProcessorsForR8(IncludeAllResources, executorService);
     feedback.updateVisibleOptimizationInfo();
 
-    printPhase("Desugared library API Conversion finalization");
-    generateDesugaredLibraryAPIWrappers(builder, executorService);
-
     if (serviceLoaderRewriter != null) {
       processSynthesizedServiceLoaderMethods(
           serviceLoaderRewriter.getServiceLoadMethods(), executorService);
@@ -984,14 +975,6 @@
     removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
   }
 
-  private void generateDesugaredLibraryAPIWrappers(
-      DexApplication.Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException {
-    if (desugaredLibraryAPIConverter != null) {
-      desugaredLibraryAPIConverter.finalizeWrappers(builder, this, executorService);
-    }
-  }
-
   private void clearDexMethodCompilationState() {
     appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
   }
@@ -1159,7 +1142,7 @@
       options.testing.hookInIrConversion.run();
     }
 
-    if (!needsIRConversion(method) || options.skipIR) {
+    if (!needsIRConversion() || options.skipIR) {
       feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
@@ -1509,23 +1492,9 @@
       timing.end();
     }
 
-    previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
-
-    // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
-    if (desugaredLibraryAPIConverter != null
-        && (!appView.enableWholeProgramOptimizations()
-            || methodProcessor.isPrimaryMethodProcessor())) {
-      timing.begin("Desugar library API");
-      desugaredLibraryAPIConverter.desugar(code);
-      timing.end();
-      assert code.isConsistentSSA();
-    }
-
-    previous = printMethod(code, "IR after desugared library API Conversion (SSA)", previous);
-
     assert code.verifyTypes(appView);
 
-    previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
+    previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
 
     // TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
     //  the list for primary processing only.
@@ -1637,6 +1606,8 @@
       MethodProcessor methodProcessor,
       MutableMethodConversionOptions conversionOptions,
       Timing timing) {
+    appView.withArgumentPropagator(
+        argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor));
 
     if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
       enumUnboxer.analyzeEnums(code, conversionOptions);
@@ -1666,8 +1637,7 @@
       timing.end();
     }
 
-    if (appView.appInfo().withLiveness().isPinned(code.context().getReference())
-        || !appView.options().isOptimizing()) {
+    if (appView.getKeepInfo().getMethodInfo(code.context()).isPinned(options)) {
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
deleted file mode 100644
index 0d1c587..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.conversion;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-
-class NeedsIRDesugarUseRegistry extends UseRegistry {
-
-  private boolean needsDesugaring = false;
-  private final ProgramMethod context;
-  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
-  public NeedsIRDesugarUseRegistry(
-      ProgramMethod method,
-      AppView<?> appView,
-      DesugaredLibraryAPIConverter desugaredLibraryAPIConverter) {
-    super(appView.dexItemFactory());
-    this.context = method;
-    this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
-  }
-
-  public boolean needsDesugaring() {
-    return needsDesugaring;
-  }
-
-  @Override
-  public void registerInitClass(DexType type) {
-    if (!needsDesugaring
-        && desugaredLibraryAPIConverter != null
-        && desugaredLibraryAPIConverter.canConvert(type)) {
-      needsDesugaring = true;
-    }
-  }
-
-  @Override
-  public void registerInvokeVirtual(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeDirect(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  private void registerDesugaredLibraryAPIConverter(DexMethod method) {
-    if (!needsDesugaring) {
-      needsDesugaring =
-          desugaredLibraryAPIConverter != null
-              && desugaredLibraryAPIConverter.shouldRewriteInvoke(method);
-    }
-  }
-
-  @Override
-  public void registerInvokeStatic(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeInterface(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInvokeStatic(DexMethod method, boolean itf) {
-    registerInvokeStatic(method);
-  }
-
-  @Override
-  public void registerCallSite(DexCallSite callSite) {
-    super.registerCallSite(callSite);
-    needsDesugaring = true;
-  }
-
-  @Override
-  public void registerInvokeSuper(DexMethod method) {
-    registerDesugaredLibraryAPIConverter(method);
-  }
-
-  @Override
-  public void registerInstanceFieldRead(DexField field) {}
-
-  @Override
-  public void registerInstanceFieldWrite(DexField field) {}
-
-  @Override
-  public void registerNewInstance(DexType type) {}
-
-  @Override
-  public void registerStaticFieldRead(DexField field) {}
-
-  @Override
-  public void registerStaticFieldWrite(DexField field) {}
-
-  @Override
-  public void registerTypeReference(DexType type) {}
-
-  @Override
-  public void registerInstanceOf(DexType type) {}
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
index 70d5123..1ddeb09 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringCollection.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.ir.desugar.records.RecordRewriter;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.List;
 
 /** Interface for desugaring a class. */
 public abstract class CfClassDesugaringCollection {
@@ -17,21 +21,39 @@
 
   public abstract boolean isEmpty();
 
-  public static class NonEmptyCfClassDesugaringCollection extends CfClassDesugaringCollection {
-    private final RecordRewriter recordRewriter;
+  public static CfClassDesugaringCollection empty() {
+    return EmptyCfClassDesugaringCollection.getInstance();
+  }
 
-    NonEmptyCfClassDesugaringCollection(RecordRewriter recordRewriter) {
-      this.recordRewriter = recordRewriter;
+  public static CfClassDesugaringCollection create(AppView<?> appView) {
+    List<CfClassDesugaring> desugarings = new ArrayList<>();
+    RecordRewriter recordRewriter = RecordRewriter.create(appView);
+    if (recordRewriter != null) {
+      desugarings.add(recordRewriter);
+    }
+    if (desugarings.isEmpty()) {
+      return empty();
+    }
+    return new NonEmptyCfClassDesugaringCollection(desugarings);
+  }
+
+  public static class NonEmptyCfClassDesugaringCollection extends CfClassDesugaringCollection {
+    private final List<CfClassDesugaring> desugarings;
+
+    NonEmptyCfClassDesugaringCollection(List<CfClassDesugaring> desugarings) {
+      this.desugarings = desugarings;
     }
 
     @Override
     public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
-      recordRewriter.desugar(clazz, eventConsumer);
+      for (CfClassDesugaring desugaring : desugarings) {
+        desugaring.desugar(clazz, eventConsumer);
+      }
     }
 
     @Override
     public boolean needsDesugaring(DexProgramClass clazz) {
-      return recordRewriter.needsDesugaring(clazz);
+      return Iterables.any(desugarings, desugaring -> desugaring.needsDesugaring(clazz));
     }
 
     @Override
@@ -41,6 +63,14 @@
   }
 
   public static class EmptyCfClassDesugaringCollection extends CfClassDesugaringCollection {
+
+    private static final EmptyCfClassDesugaringCollection INSTANCE =
+        new EmptyCfClassDesugaringCollection();
+
+    public static EmptyCfClassDesugaringCollection getInstance() {
+      return INSTANCE;
+    }
+
     @Override
     public void desugar(DexProgramClass clazz, CfClassDesugaringEventConsumer eventConsumer) {
       // Intentionally empty.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 8512734..10032bc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 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.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 /**
  * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -52,8 +54,6 @@
     return false;
   }
 
-  public abstract CfClassDesugaringCollection createClassDesugaringCollection();
-
   /** Returns true if the given method needs desugaring. */
   public abstract boolean needsDesugaring(ProgramMethod method);
 
@@ -64,4 +64,7 @@
       Flavor flavor);
 
   public abstract RetargetingInfo getRetargetingInfo();
+
+  public abstract void withDesugaredLibraryAPIConverter(
+      Consumer<DesugaredLibraryAPIConverter> consumer);
 }
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 d497332..0b29d90 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
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
@@ -48,7 +49,8 @@
         RecordDesugaringEventConsumer,
         TwrCloseResourceDesugaringEventConsumer,
         InterfaceMethodDesugaringEventConsumer,
-        DesugaredLibraryRetargeterInstructionEventConsumer {
+        DesugaredLibraryRetargeterInstructionEventConsumer,
+        DesugaredLibraryAPIConverterEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -68,6 +70,21 @@
     return new CfInstructionDesugaringEventConsumer() {
 
       @Override
+      public void acceptWrapperProgramClass(DexProgramClass clazz) {
+        assert false;
+      }
+
+      @Override
+      public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+        assert false;
+      }
+
+      @Override
+      public void acceptAPIConversion(ProgramMethod method) {
+        assert false;
+      }
+
+      @Override
       public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
         assert false;
       }
@@ -229,6 +246,21 @@
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
+    @Override
+    public void acceptWrapperProgramClass(DexProgramClass clazz) {
+      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void acceptAPIConversion(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
     public List<ProgramMethod> finalizeDesugaring(
         AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
       List<ProgramMethod> needsProcessing = new ArrayList<>();
@@ -348,6 +380,23 @@
     }
 
     @Override
+    public void acceptWrapperProgramClass(DexProgramClass clazz) {
+      // Called only in Desugared library compilation which is D8.
+      assert false;
+    }
+
+    @Override
+    public void acceptWrapperClasspathClass(DexClasspathClass clazz) {
+      additions.addLiveClasspathClass(clazz);
+    }
+
+    @Override
+    public void acceptAPIConversion(ProgramMethod method) {
+      // Intentionally empty. The method will be hit by the tracing in R8 as if it was
+      // present in the input code, and thus nothing needs to be done.
+    }
+
+    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
       // present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
index 38a0413..f3aaaad 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaring.java
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
 public interface CfPostProcessingDesugaring {
 
   void postProcessingDesugaring(
-      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      Collection<DexProgramClass> programClasses,
+      CfPostProcessingDesugaringEventConsumer eventConsumer,
+      ExecutorService executorService)
       throws ExecutionException;
 }
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 986c6ea..e539861 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
@@ -4,10 +4,13 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPICallbackSynthesizor;
 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 java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -30,7 +33,9 @@
   }
 
   public abstract void postProcessingDesugaring(
-      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      Collection<DexProgramClass> programClasses,
+      CfPostProcessingDesugaringEventConsumer eventConsumer,
+      ExecutorService executorService)
       throws ExecutionException;
 
   public static class NonEmptyCfPostProcessingDesugaringCollection
@@ -47,10 +52,6 @@
         AppView<?> appView,
         InterfaceMethodProcessorFacade interfaceMethodProcessorFacade,
         RetargetingInfo retargetingInfo) {
-      if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
-          && interfaceMethodProcessorFacade == null) {
-        return empty();
-      }
       ArrayList<CfPostProcessingDesugaring> desugarings = new ArrayList<>();
       if (!appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
         desugarings.add(new DesugaredLibraryRetargeterPostProcessor(appView, retargetingInfo));
@@ -58,15 +59,29 @@
       if (interfaceMethodProcessorFacade != null) {
         desugarings.add(interfaceMethodProcessorFacade);
       }
+      DesugaredLibraryAPICallbackSynthesizor apiCallbackSynthesizor =
+          appView.rewritePrefix.isRewriting()
+              ? new DesugaredLibraryAPICallbackSynthesizor(appView)
+              : null;
+      // At this point the desugaredLibraryAPIConverter is required to be last to generate
+      // call-backs on the forwarding methods.
+      if (apiCallbackSynthesizor != null) {
+        desugarings.add(apiCallbackSynthesizor);
+      }
+      if (desugarings.isEmpty()) {
+        return empty();
+      }
       return new NonEmptyCfPostProcessingDesugaringCollection(desugarings);
     }
 
     @Override
     public void postProcessingDesugaring(
-        CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+        Collection<DexProgramClass> programClasses,
+        CfPostProcessingDesugaringEventConsumer eventConsumer,
+        ExecutorService executorService)
         throws ExecutionException {
       for (CfPostProcessingDesugaring desugaring : desugarings) {
-        desugaring.postProcessingDesugaring(eventConsumer, executorService);
+        desugaring.postProcessingDesugaring(programClasses, eventConsumer, executorService);
       }
     }
   }
@@ -85,7 +100,9 @@
 
     @Override
     public void postProcessingDesugaring(
-        CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+        Collection<DexProgramClass> programClasses,
+        CfPostProcessingDesugaringEventConsumer eventConsumer,
+        ExecutorService executorService)
         throws ExecutionException {
       // Intentionally empty.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 5734cab..d0b798b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -3,13 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessingDesugaringEventConsumer;
 import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
@@ -23,21 +22,16 @@
  */
 public abstract class CfPostProcessingDesugaringEventConsumer
     implements DesugaredLibraryRetargeterPostProcessingEventConsumer,
-        InterfaceProcessingDesugaringEventConsumer {
-  protected DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
-
-  protected CfPostProcessingDesugaringEventConsumer(AppView<?> appView) {
-    this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView, null);
-  }
+        InterfaceProcessingDesugaringEventConsumer,
+        DesugaredLibraryAPIConverterPostProcessingEventConsumer {
 
   public static D8CfPostProcessingDesugaringEventConsumer createForD8(
-      D8MethodProcessor methodProcessor, AppView<?> appView) {
-    return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, appView);
+      D8MethodProcessor methodProcessor) {
+    return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor);
   }
 
-  public static R8PostProcessingDesugaringEventConsumer createForR8(
-      AppView<?> appView, SyntheticAdditions additions) {
-    return new R8PostProcessingDesugaringEventConsumer(appView, additions);
+  public static R8PostProcessingDesugaringEventConsumer createForR8(SyntheticAdditions additions) {
+    return new R8PostProcessingDesugaringEventConsumer(additions);
   }
 
   public abstract void finalizeDesugaring() throws ExecutionException;
@@ -49,9 +43,7 @@
     // concurrently processing other methods.
     private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
 
-    private D8CfPostProcessingDesugaringEventConsumer(
-        D8MethodProcessor methodProcessor, AppView<?> appView) {
-      super(appView);
+    private D8CfPostProcessingDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
       this.methodProcessor = methodProcessor;
     }
 
@@ -92,21 +84,24 @@
       methodProcessor.scheduleDesugaredMethodsForProcessing(methodsToReprocess);
       methodProcessor.awaitMethodProcessing();
     }
+
+    @Override
+    public void acceptAPIConversionCallback(ProgramMethod method) {
+      methodsToReprocess.add(method);
+    }
   }
 
   public static class R8PostProcessingDesugaringEventConsumer
       extends CfPostProcessingDesugaringEventConsumer {
     private final SyntheticAdditions additions;
 
-    protected R8PostProcessingDesugaringEventConsumer(
-        AppView<?> appView, SyntheticAdditions additions) {
-      super(appView);
+    R8PostProcessingDesugaringEventConsumer(SyntheticAdditions additions) {
       this.additions = additions;
     }
 
     @Override
     public void finalizeDesugaring() throws ExecutionException {
-      desugaredLibraryAPIConverter.generateTrackingWarnings();
+      // Intentionally empty.
     }
 
     @Override
@@ -127,10 +122,6 @@
     @Override
     public void acceptForwardingMethod(ProgramMethod method) {
       additions.addLiveMethod(method);
-      ProgramMethod callback = desugaredLibraryAPIConverter.generateCallbackIfRequired(method);
-      if (callback != null) {
-        additions.addLiveMethod(callback);
-      }
     }
 
     @Override
@@ -142,5 +133,10 @@
     public void acceptEmulatedInterfaceMethod(ProgramMethod method) {
       assert false : "TODO(b/183998768): Support Interface processing in R8";
     }
+
+    @Override
+    public void acceptAPIConversionCallback(ProgramMethod method) {
+      additions.addLiveMethod(method);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 7991ac3..b23f31e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -6,12 +6,13 @@
 
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 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.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.function.Consumer;
 
 public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -49,11 +50,6 @@
   }
 
   @Override
-  public CfClassDesugaringCollection createClassDesugaringCollection() {
-    return new EmptyCfClassDesugaringCollection();
-  }
-
-  @Override
   public boolean needsDesugaring(ProgramMethod method) {
     return false;
   }
@@ -73,4 +69,9 @@
   public RetargetingInfo getRetargetingInfo() {
     return null;
   }
+
+  @Override
+  public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+    // Intentionally empty.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 2d53448..88f451b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,10 +29,10 @@
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer;
@@ -321,7 +321,7 @@
     assert implMethod.holder == accessedFrom.getHolderType();
     assert descriptor.verifyTargetFoundInClass(accessedFrom.getHolderType());
     if (implHandle.type.isInvokeStatic()) {
-      ResolutionResult resolution =
+      MethodResolutionResult resolution =
           appView.appInfoForDesugaring().resolveMethod(implMethod, implHandle.isInterface);
       if (resolution.isFailedResolution()) {
         return new InvalidLambdaImplTarget(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 6975252..e0a9c07 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -407,7 +407,7 @@
       }
       DexType fromTypeAsPrimitive = factory.getPrimitiveFromBoxed(boxedType);
       if (fromTypeAsPrimitive != null) {
-        addPrimitiveUnboxing(fromTypeAsPrimitive, boxedType, instructions, factory);
+        addPrimitiveUnboxing(boxedType, instructions, factory);
         addPrimitiveWideningConversion(fromTypeAsPrimitive, toType, instructions);
         return;
       }
@@ -424,7 +424,7 @@
           || (boxedFromType != factory.booleanType
               && boxedFromType != factory.charType
               && toType == factory.boxedNumberType)) {
-        addPrimitiveBoxing(fromType, boxedFromType, instructions, factory);
+        addPrimitiveBoxing(boxedFromType, instructions, factory);
         return;
       }
     }
@@ -513,55 +513,19 @@
         "converted to " + toType.toSourceString() + " via primitive widening conversion.");
   }
 
-  private static DexMethod getUnboxMethod(byte primitive, DexType boxType, DexItemFactory factory) {
-    DexProto proto;
-    switch (primitive) {
-      case 'Z':  // byte
-        proto = factory.createProto(factory.booleanType);
-        return factory.createMethod(boxType, proto, factory.unboxBooleanMethodName);
-      case 'B':  // byte
-        proto = factory.createProto(factory.byteType);
-        return factory.createMethod(boxType, proto, factory.unboxByteMethodName);
-      case 'S':  // short
-        proto = factory.createProto(factory.shortType);
-        return factory.createMethod(boxType, proto, factory.unboxShortMethodName);
-      case 'C':  // char
-        proto = factory.createProto(factory.charType);
-        return factory.createMethod(boxType, proto, factory.unboxCharMethodName);
-      case 'I':  // int
-        proto = factory.createProto(factory.intType);
-        return factory.createMethod(boxType, proto, factory.unboxIntMethodName);
-      case 'J':  // long
-        proto = factory.createProto(factory.longType);
-        return factory.createMethod(boxType, proto, factory.unboxLongMethodName);
-      case 'F':  // float
-        proto = factory.createProto(factory.floatType);
-        return factory.createMethod(boxType, proto, factory.unboxFloatMethodName);
-      case 'D':  // double
-        proto = factory.createProto(factory.doubleType);
-        return factory.createMethod(boxType, proto, factory.unboxDoubleMethodName);
-      default:
-        throw new Unreachable("Invalid primitive type descriptor: " + primitive);
-    }
-  }
-
   private static void addPrimitiveUnboxing(
-      DexType primitiveType,
       DexType boxType,
       Builder<CfInstruction> instructions,
       DexItemFactory factory) {
-    DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType, factory);
+    DexMethod method = factory.getUnboxPrimitiveMethod(boxType);
     instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
   }
 
   private static void addPrimitiveBoxing(
-      DexType primitiveType,
       DexType boxType,
       Builder<CfInstruction> instructions,
       DexItemFactory factory) {
-    // Generate factory method fo boxing.
-    DexProto proto = factory.createProto(boxType, primitiveType);
-    DexMethod method = factory.createMethod(boxType, proto, factory.valueOfMethodName);
+    DexMethod method = factory.getBoxPrimitiveMethod(boxType);
     instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
   }
 }
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 a7f1a33..c4ce73e 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
@@ -12,8 +12,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
-import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
@@ -35,6 +34,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -45,6 +45,7 @@
   private final RecordRewriter recordRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
 
   NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
     this.appView = appView;
@@ -53,6 +54,7 @@
       this.recordRewriter = null;
       this.desugaredLibraryRetargeter = null;
       this.interfaceMethodRewriter = null;
+      this.desugaredLibraryAPIConverter = null;
       return;
     }
     this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
@@ -72,14 +74,32 @@
       desugarings.add(new TwrInstructionDesugaring(appView));
     }
     // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
-    interfaceMethodRewriter =
-        appView.options().isInterfaceMethodDesugaringEnabled()
-                && !appView.enableWholeProgramOptimizations()
+    if (appView.options().isInterfaceMethodDesugaringEnabled()
+        && !appView.enableWholeProgramOptimizations()) {
+      interfaceMethodRewriter =
+          new InterfaceMethodRewriter(
+              appView, backportedMethodRewriter, desugaredLibraryRetargeter);
+      desugarings.add(interfaceMethodRewriter);
+    } else {
+      interfaceMethodRewriter = null;
+    }
+    // In R8 interface method rewriting is performed in IR, we still need to filter
+    // out from API conversion methods desugared by the interface method rewriter.
+    InterfaceMethodRewriter enforcedInterfaceMethodRewriter =
+        interfaceMethodRewriter == null && appView.options().isInterfaceMethodDesugaringEnabled()
             ? new InterfaceMethodRewriter(
                 appView, backportedMethodRewriter, desugaredLibraryRetargeter)
+            : interfaceMethodRewriter;
+    desugaredLibraryAPIConverter =
+        appView.rewritePrefix.isRewriting()
+            ? new DesugaredLibraryAPIConverter(
+                appView,
+                enforcedInterfaceMethodRewriter,
+                desugaredLibraryRetargeter,
+                backportedMethodRewriter)
             : null;
-    if (interfaceMethodRewriter != null) {
-      desugarings.add(interfaceMethodRewriter);
+    if (desugaredLibraryAPIConverter != null) {
+      desugarings.add(desugaredLibraryAPIConverter);
     }
     desugarings.add(new LambdaInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
@@ -220,14 +240,6 @@
     return true;
   }
 
-  @Override
-  public CfClassDesugaringCollection createClassDesugaringCollection() {
-    if (recordRewriter == null) {
-      return new EmptyCfClassDesugaringCollection();
-    }
-    return new NonEmptyCfClassDesugaringCollection(recordRewriter);
-  }
-
   private Collection<CfInstruction> desugarInstruction(
       CfInstruction instruction,
       FreshLocalProvider freshLocalProvider,
@@ -332,4 +344,11 @@
     }
     return null;
   }
+
+  @Override
+  public void withDesugaredLibraryAPIConverter(Consumer<DesugaredLibraryAPIConverter> consumer) {
+    if (desugaredLibraryAPIConverter != null) {
+      consumer.accept(desugaredLibraryAPIConverter);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPICallbackSynthesizor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPICallbackSynthesizor.java
new file mode 100644
index 0000000..cfcb9b0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPICallbackSynthesizor.java
@@ -0,0 +1,223 @@
+// 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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.generateTrackDesugaredAPIWarnings;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.isAPIConversionSyntheticType;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexClass;
+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.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
+import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer.DesugaredLibraryAPIConverterPostProcessingEventConsumer;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+public class DesugaredLibraryAPICallbackSynthesizor implements CfPostProcessingDesugaring {
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+
+  private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
+  private final Set<DexMethod> trackedCallBackAPIs;
+
+  public DesugaredLibraryAPICallbackSynthesizor(AppView<?> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+    this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView);
+    if (appView.options().testing.trackDesugaredAPIConversions) {
+      trackedCallBackAPIs = Sets.newConcurrentHashSet();
+    } else {
+      trackedCallBackAPIs = null;
+    }
+  }
+
+  // TODO(b/191656218): Consider parallelizing post processing.
+  @Override
+  public void postProcessingDesugaring(
+      Collection<DexProgramClass> programClasses,
+      CfPostProcessingDesugaringEventConsumer eventConsumer,
+      ExecutorService executorService) {
+    assert noPendingWrappersOrConversions();
+    for (DexProgramClass clazz : programClasses) {
+      if (!appView.isAlreadyLibraryDesugared(clazz)) {
+        ArrayList<DexEncodedMethod> callbacks = new ArrayList<>();
+        // We note that methods requiring callbacks are library overrides, therefore, they should
+        // always be live in R8.
+        for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
+          if (shouldRegisterCallback(virtualProgramMethod)) {
+            if (trackedCallBackAPIs != null) {
+              trackedCallBackAPIs.add(virtualProgramMethod.getReference());
+            }
+            ProgramMethod callback =
+                generateCallbackMethod(
+                    virtualProgramMethod.getDefinition(),
+                    virtualProgramMethod.getHolder(),
+                    eventConsumer);
+            callbacks.add(callback.getDefinition());
+          }
+        }
+        if (!callbacks.isEmpty()) {
+          clazz.addVirtualMethods(callbacks);
+        }
+      }
+    }
+    assert noPendingWrappersOrConversions();
+    generateTrackingWarnings();
+  }
+
+  private boolean noPendingWrappersOrConversions() {
+    for (DexProgramClass pendingSyntheticClass :
+        appView.getSyntheticItems().getPendingSyntheticClasses()) {
+      assert !isAPIConversionSyntheticType(pendingSyntheticClass.type, wrapperSynthesizor, appView);
+    }
+    return true;
+  }
+
+  public boolean shouldRegisterCallback(ProgramMethod method) {
+    // Any override of a library method can be called by the library.
+    // We duplicate the method to have a vivified type version callable by the library and
+    // a type version callable by the program. We need to add the vivified version to the rootset
+    // as it is actually overriding a library method (after changing the vivified type to the core
+    // library type), but the enqueuer cannot see that.
+    // To avoid too much computation we first look if the method would need to be rewritten if
+    // it would override a library method, then check if it overrides a library method.
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.isPrivateMethod()
+        || definition.isStatic()
+        || definition.isAbstract()
+        || definition.isLibraryMethodOverride().isFalse()) {
+      return false;
+    }
+    if (!appView.rewritePrefix.hasRewrittenTypeInSignature(definition.getProto(), appView)
+        || appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getEmulateLibraryInterface()
+            .containsKey(method.getHolderType())) {
+      return false;
+    }
+    // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
+    // methods will be desugared.
+    // In D8, this happens after interface method desugaring, we cannot introduce new default
+    // methods, but we do not need to since this is a library override (invokes will resolve) and
+    // all implementors have been enhanced with a forwarding method which will be duplicated.
+    if (!appView.enableWholeProgramOptimizations()) {
+      if (method.getHolder().isInterface()
+          && method.getDefinition().isDefaultMethod()
+          && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
+              || appView.options().isDesugaredLibraryCompilation())) {
+        return false;
+      }
+    }
+    if (!appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary
+        && appView.options().isDesugaredLibraryCompilation()) {
+      return false;
+    }
+    return overridesNonFinalLibraryMethod(method);
+  }
+
+  private boolean overridesNonFinalLibraryMethod(ProgramMethod method) {
+    // We look up everywhere to see if there is a supertype/interface implementing the method...
+    DexProgramClass holder = method.getHolder();
+    WorkList<DexType> workList = WorkList.newIdentityWorkList();
+    workList.addIfNotSeen(holder.interfaces.values);
+    boolean foundOverrideToRewrite = false;
+    // There is no methods with desugared types on Object.
+    if (holder.superType != factory.objectType) {
+      workList.addIfNotSeen(holder.superType);
+    }
+    while (workList.hasNext()) {
+      DexType current = workList.next();
+      DexClass dexClass = appView.definitionFor(current);
+      if (dexClass == null) {
+        continue;
+      }
+      workList.addIfNotSeen(dexClass.interfaces.values);
+      if (dexClass.superType != factory.objectType) {
+        workList.addIfNotSeen(dexClass.superType);
+      }
+      if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
+        continue;
+      }
+      if (!shouldGenerateCallbacksForEmulateInterfaceAPIs(dexClass)) {
+        continue;
+      }
+      DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method.getReference());
+      if (dexEncodedMethod != null) {
+        // In this case, the object will be wrapped.
+        if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
+          return false;
+        }
+        if (dexEncodedMethod.isFinal()) {
+          // We do not introduce overrides of final methods, in this case, the runtime always
+          // execute the default behavior in the final method.
+          return false;
+        }
+        foundOverrideToRewrite = true;
+      }
+    }
+    return foundOverrideToRewrite;
+  }
+
+  private boolean shouldGenerateCallbacksForEmulateInterfaceAPIs(DexClass dexClass) {
+    if (appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary) {
+      return true;
+    }
+    Map<DexType, DexType> emulateLibraryInterfaces =
+        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+    return !(emulateLibraryInterfaces.containsKey(dexClass.type)
+        || emulateLibraryInterfaces.containsValue(dexClass.type));
+  }
+
+  private ProgramMethod generateCallbackMethod(
+      DexEncodedMethod originalMethod,
+      DexProgramClass clazz,
+      DesugaredLibraryAPIConverterPostProcessingEventConsumer eventConsumer) {
+    DexMethod methodToInstall =
+        methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
+    CfCode cfCode =
+        new APIConverterWrapperCfCodeProvider(
+                appView,
+                originalMethod.getReference(),
+                null,
+                wrapperSynthesizor,
+                clazz.isInterface(),
+                null)
+            .generateCfCode();
+    DexEncodedMethod newMethod =
+        wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+    newMethod.setCode(cfCode, appView);
+    if (originalMethod.isLibraryMethodOverride().isTrue()) {
+      newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+    }
+    ProgramMethod callback = new ProgramMethod(clazz, newMethod);
+    if (eventConsumer != null) {
+      eventConsumer.acceptAPIConversionCallback(callback);
+    } else {
+      assert appView.enableWholeProgramOptimizations();
+    }
+    return callback;
+  }
+
+  private void generateTrackingWarnings() {
+    generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ", appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
index b50ac86..ebab39e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
@@ -4,49 +4,47 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexClasspathClass;
-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.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock;
-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.Invoke;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
 
 // I convert library calls with desugared parameters/return values so they can work normally.
 // In the JSON of the desugared library, one can specify conversions between desugared and
@@ -62,33 +60,33 @@
 // DesugarType is only a rewritten type (generated through rewriting of type).
 // The type, from the library, may either be rewritten to the desugarType,
 // or be a rewritten type (generated through rewriting of vivifiedType).
-public class DesugaredLibraryAPIConverter {
+public class DesugaredLibraryAPIConverter implements CfInstructionDesugaring {
 
   static final String VIVIFIED_PREFIX = "$-vivified-$.";
   public static final String DESCRIPTOR_VIVIFIED_PREFIX = "L$-vivified-$/";
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
-  // For debugging only, allows to assert that synthesized code in R8 have been synthesized in the
-  // Enqueuer and not during IR processing.
-  private final Mode mode;
+  // This is used to filter out double desugaring on backported methods.
+  private final BackportedMethodRewriter backportedMethodRewriter;
+  private final InterfaceMethodRewriter interfaceMethodRewriter;
+  private final DesugaredLibraryRetargeter retargeter;
+
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
-  private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new IdentityHashMap<>();
-  private final Map<DexProgramClass, List<DexEncodedMethod>> pendingCallBackMethods =
-      new IdentityHashMap<>();
   private final Set<DexMethod> trackedCallBackAPIs;
   private final Set<DexMethod> trackedAPIs;
 
-  public enum Mode {
-    GENERATE_CALLBACKS_AND_WRAPPERS,
-    ASSERT_CALLBACKS_AND_WRAPPERS_GENERATED;
-  }
-
-  public DesugaredLibraryAPIConverter(AppView<?> appView, Mode mode) {
+  public DesugaredLibraryAPIConverter(
+      AppView<?> appView,
+      InterfaceMethodRewriter interfaceMethodRewriter,
+      DesugaredLibraryRetargeter retargeter,
+      BackportedMethodRewriter backportedMethodRewriter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
-    this.mode = mode;
-    this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView, this);
+    this.interfaceMethodRewriter = interfaceMethodRewriter;
+    this.retargeter = retargeter;
+    this.backportedMethodRewriter = backportedMethodRewriter;
+    this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView);
     if (appView.options().testing.trackDesugaredAPIConversions) {
       trackedCallBackAPIs = Sets.newConcurrentHashSet();
       trackedAPIs = Sets.newConcurrentHashSet();
@@ -98,209 +96,131 @@
     }
   }
 
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    if (needsDesugaring(instruction, context)) {
+      assert instruction.isInvoke();
+      return rewriteLibraryInvoke(
+          instruction.asInvoke(),
+          methodProcessingContext,
+          localStackAllocator,
+          eventConsumer,
+          context);
+    }
+    return null;
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    if (!instruction.isInvoke()) {
+      return false;
+    }
+    if (isAPIConversionSyntheticType(context.getHolderType(), wrapperSynthesizor, appView)) {
+      return false;
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    return shouldRewriteInvoke(
+        invoke.getMethod(), invoke.getInvokeType(context), invoke.isInterface(), context);
+  }
+
+  static boolean isAPIConversionSyntheticType(
+      DexType type, DesugaredLibraryWrapperSynthesizer wrapperSynthesizor, AppView<?> appView) {
+    return wrapperSynthesizor.isSyntheticWrapper(type)
+        || appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.API_CONVERSION);
+  }
+
   public static boolean isVivifiedType(DexType type) {
     return type.descriptor.toString().startsWith(DESCRIPTOR_VIVIFIED_PREFIX);
   }
 
-  boolean canGenerateWrappersAndCallbacks() {
-    return mode == Mode.GENERATE_CALLBACKS_AND_WRAPPERS;
+  private DexClassAndMethod getMethodForDesugaring(
+      DexMethod invokedMethod, boolean isInvokeSuper, boolean isInterface, ProgramMethod context) {
+    // TODO(b/191656218): Use lookupInvokeSpecial instead when this is all to Cf.
+    return isInvokeSuper
+        ? appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context)
+        : appView
+            .appInfoForDesugaring()
+            .resolveMethod(invokedMethod, isInterface)
+            .getResolutionPair();
   }
 
-  public void desugar(IRCode code) {
-
-    if (wrapperSynthesizor.isSyntheticWrapper(code.method().getHolderType())) {
-      return;
-    }
-
-    if (!canGenerateWrappersAndCallbacks()) {
-      assert validateCallbackWasGeneratedInEnqueuer(code.context());
-    } else {
-      registerCallbackIfRequired(code.context());
-    }
-
-    ListIterator<BasicBlock> blockIterator = code.listIterator();
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      InstructionListIterator iterator = block.listIterator(code);
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        if (!instruction.isInvokeMethod()) {
-          continue;
-        }
-        InvokeMethod invokeMethod = instruction.asInvokeMethod();
-        DexMethod invokedMethod;
-        if (invokeMethod.isInvokeSuper()) {
-          DexClassAndMethod result =
-              appView
-                  .appInfoForDesugaring()
-                  .lookupSuperTarget(invokeMethod.getInvokedMethod(), code.context());
-          invokedMethod = result != null ? result.getReference() : null;
-        } else {
-          // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
-          invokedMethod = invokeMethod.getInvokedMethod();
-        }
-        // Library methods do not understand desugared types, hence desugared types have to be
-        // converted around non desugared library calls for the invoke to resolve.
-        if (invokedMethod != null && shouldRewriteInvoke(invokedMethod)) {
-          rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
-        }
-      }
-    }
-  }
-
-  private boolean validateCallbackWasGeneratedInEnqueuer(ProgramMethod method) {
-    if (!shouldRegisterCallback(method)) {
-      return true;
-    }
-    DexMethod installedCallback = methodWithVivifiedTypeInSignature(method, appView);
-    assert method.getHolder().lookupMethod(installedCallback) != null;
-    return true;
-  }
-
-  public boolean shouldRewriteInvoke(DexMethod invokedMethod) {
-    if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView)
-        || invokedMethod.holder.isArrayType()) {
+  // TODO(b/191656218): Consider caching the result.
+  private boolean shouldRewriteInvoke(
+      DexMethod unresolvedInvokedMethod,
+      Type invokeType,
+      boolean isInterface,
+      ProgramMethod context) {
+    DexClassAndMethod invokedMethod =
+        getMethodForDesugaring(
+            unresolvedInvokedMethod, invokeType == Type.SUPER, isInterface, context);
+    if (invokedMethod == null) {
+      // Implies a resolution/look-up failure, we do not convert to keep the runtime error.
       return false;
     }
-    DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+    DexType holderType = invokedMethod.getHolderType();
+    if (appView.rewritePrefix.hasRewrittenType(holderType, appView) || holderType.isArrayType()) {
+      return false;
+    }
+    DexClass dexClass = appView.definitionFor(holderType);
     if (dexClass == null || !dexClass.isLibraryClass()) {
       return false;
     }
-    return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView);
-  }
-
-  public void registerCallbackIfRequired(ProgramMethod method) {
-    if (shouldRegisterCallback(method)) {
-      registerCallback(method);
-    }
-  }
-
-  public ProgramMethod generateCallbackIfRequired(ProgramMethod method) {
-    if (!shouldRegisterCallback(method)) {
-      return null;
-    }
-    if (trackedCallBackAPIs != null) {
-      trackedCallBackAPIs.add(method.getReference());
-    }
-    ProgramMethod callback = generateCallbackMethod(method.getDefinition(), method.getHolder());
-    method.getHolder().addVirtualMethod(callback.getDefinition());
-    return callback;
-  }
-
-  public boolean shouldRegisterCallback(ProgramMethod method) {
-    // Any override of a library method can be called by the library.
-    // We duplicate the method to have a vivified type version callable by the library and
-    // a type version callable by the program. We need to add the vivified version to the rootset
-    // as it is actually overriding a library method (after changing the vivified type to the core
-    // library type), but the enqueuer cannot see that.
-    // To avoid too much computation we first look if the method would need to be rewritten if
-    // it would override a library method, then check if it overrides a library method.
-    DexEncodedMethod definition = method.getDefinition();
-    if (definition.isPrivateMethod()
-        || definition.isStatic()
-        || definition.isLibraryMethodOverride().isFalse()) {
+    if (isEmulatedInterfaceOverride(invokedMethod)) {
       return false;
     }
-    if (!appView.rewritePrefix.hasRewrittenTypeInSignature(definition.getProto(), appView)
-        || appView
+    if (isAlreadyDesugared(unresolvedInvokedMethod, invokeType, isInterface, context)) {
+      return false;
+    }
+    return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.getProto(), appView);
+  }
+
+  // The problem is that a method can resolve into a library method which is not present at runtime,
+  // the code relies in that case on emulated interface dispatch. We should not convert such API.
+  private boolean isEmulatedInterfaceOverride(DexClassAndMethod invokedMethod) {
+    if (interfaceMethodRewriter == null) {
+      return false;
+    }
+    if (!interfaceMethodRewriter.getEmulatedMethods().contains(invokedMethod.getName())) {
+      return false;
+    }
+    DexClassAndMethod interfaceResult =
+        appView
+            .appInfoForDesugaring()
+            .lookupMaximallySpecificMethod(invokedMethod.getHolder(), invokedMethod.getReference());
+    return interfaceResult != null
+        && appView
             .options()
             .desugaredLibraryConfiguration
             .getEmulateLibraryInterface()
-            .containsKey(method.getHolderType())) {
-      return false;
-    }
-    // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
-    // methods will be desugared.
-    // In D8, this happens after interface method desugaring, we cannot introduce new default
-    // methods, but we do not need to since this is a library override (invokes will resolve) and
-    // all implementors have been enhanced with a forwarding method which will be duplicated.
-    if (!appView.enableWholeProgramOptimizations()) {
-      if (method.getHolder().isInterface()
-          && method.getDefinition().isDefaultMethod()
-          && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
-              || appView.options().isDesugaredLibraryCompilation())) {
-        return false;
-      }
-    }
-    if (!appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary
-        && appView.options().isDesugaredLibraryCompilation()) {
-      return false;
-    }
-    return overridesNonFinalLibraryMethod(method);
+            .containsKey(interfaceResult.getHolderType());
   }
 
-  private boolean overridesNonFinalLibraryMethod(ProgramMethod method) {
-    // We look up everywhere to see if there is a supertype/interface implementing the method...
-    DexProgramClass holder = method.getHolder();
-    WorkList<DexType> workList = WorkList.newIdentityWorkList();
-    workList.addIfNotSeen(holder.interfaces.values);
-    boolean foundOverrideToRewrite = false;
-    // There is no methods with desugared types on Object.
-    if (holder.superType != factory.objectType) {
-      workList.addIfNotSeen(holder.superType);
-    }
-    while (workList.hasNext()) {
-      DexType current = workList.next();
-      DexClass dexClass = appView.definitionFor(current);
-      if (dexClass == null) {
-        continue;
-      }
-      workList.addIfNotSeen(dexClass.interfaces.values);
-      if (dexClass.superType != factory.objectType) {
-        workList.addIfNotSeen(dexClass.superType);
-      }
-      if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
-        continue;
-      }
-      if (!shouldGenerateCallbacksForEmulateInterfaceAPIs(dexClass)) {
-        continue;
-      }
-      DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method.getReference());
-      if (dexEncodedMethod != null) {
-        // In this case, the object will be wrapped.
-        if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
-          return false;
-        }
-        if (dexEncodedMethod.isFinal()) {
-          // We do not introduce overrides of final methods, in this case, the runtime always
-          // execute the default behavior in the final method.
-          return false;
-        }
-        foundOverrideToRewrite = true;
-      }
-    }
-    return foundOverrideToRewrite;
-  }
-
-  private boolean shouldGenerateCallbacksForEmulateInterfaceAPIs(DexClass dexClass) {
-    if (appView.options().desugaredLibraryConfiguration.supportAllCallbacksFromLibrary) {
+  private boolean isAlreadyDesugared(
+      DexMethod unresolvedInvokedMethod,
+      Type invokeType,
+      boolean isInterface,
+      ProgramMethod context) {
+    if (interfaceMethodRewriter != null
+        && interfaceMethodRewriter.needsRewriting(unresolvedInvokedMethod, invokeType, context)) {
       return true;
     }
-    Map<DexType, DexType> emulateLibraryInterfaces =
-        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
-    return !(emulateLibraryInterfaces.containsKey(dexClass.type)
-        || emulateLibraryInterfaces.containsValue(dexClass.type));
-  }
-
-  private synchronized void registerCallback(ProgramMethod method) {
-    if (trackedCallBackAPIs != null) {
-      trackedCallBackAPIs.add(method.getReference());
+    if (retargeter != null
+        && retargeter.hasNewInvokeTarget(
+            unresolvedInvokedMethod, isInterface, invokeType == Type.SUPER, context)) {
+      return true;
     }
-    addCallBackSignature(method);
-  }
-
-  private synchronized void addCallBackSignature(ProgramMethod method) {
-    DexProgramClass holder = method.getHolder();
-    DexEncodedMethod definition = method.getDefinition();
-    if (callBackMethods.computeIfAbsent(holder, key -> Sets.newIdentityHashSet()).add(definition)) {
-      pendingCallBackMethods.computeIfAbsent(holder, key -> new ArrayList<>()).add(definition);
+    if (backportedMethodRewriter != null
+        && backportedMethodRewriter.methodIsBackport(unresolvedInvokedMethod)) {
+      return true;
     }
-  }
-
-  public static DexMethod methodWithVivifiedTypeInSignature(
-      ProgramMethod method, AppView<?> appView) {
-    return methodWithVivifiedTypeInSignature(
-        method.getReference(), method.getHolderType(), appView);
+    return false;
   }
 
   public static DexMethod methodWithVivifiedTypeInSignature(
@@ -322,70 +242,20 @@
     return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
   }
 
-  public void finalizeWrappers(
-      DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
-      throws ExecutionException {
-    // In D8, we generate the wrappers here. In R8, wrappers have already been generated in the
-    // enqueuer, so nothing needs to be done.
-    if (appView.enableWholeProgramOptimizations()) {
-      return;
-    }
-    SortedProgramMethodSet callbacks = generateCallbackMethods();
-    irConverter.processMethodsConcurrently(callbacks, executorService);
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      wrapperSynthesizor.finalizeWrappersForL8();
-    }
-  }
-
-  public SortedProgramMethodSet generateCallbackMethods() {
-    generateTrackingWarnings();
-    SortedProgramMethodSet allCallbackMethods = SortedProgramMethodSet.create();
-    pendingCallBackMethods.forEach(
-        (clazz, callbacks) -> {
-          List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
-          callbacks.forEach(
-              callback -> {
-                ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz);
-                newVirtualMethods.add(callbackMethod.getDefinition());
-                allCallbackMethods.add(callbackMethod);
-              });
-          clazz.addVirtualMethods(newVirtualMethods);
-        });
-    pendingCallBackMethods.clear();
-    return allCallbackMethods;
+  public void ensureWrappersForL8(CfInstructionDesugaringEventConsumer eventConsumer) {
+    assert appView.options().isDesugaredLibraryCompilation();
+    wrapperSynthesizor.ensureWrappersForL8(eventConsumer);
   }
 
   public void generateTrackingWarnings() {
-    if (appView.options().testing.trackDesugaredAPIConversions) {
-      generateTrackDesugaredAPIWarnings(trackedAPIs, "");
-      generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
-      trackedAPIs.clear();
-      trackedCallBackAPIs.clear();
+    generateTrackDesugaredAPIWarnings(trackedAPIs, "", appView);
+  }
+
+  static void generateTrackDesugaredAPIWarnings(
+      Set<DexMethod> tracked, String inner, AppView<?> appView) {
+    if (!appView.options().testing.trackDesugaredAPIConversions) {
+      return;
     }
-  }
-
-  public void synthesizeWrappers(Consumer<DexClasspathClass> synthesizedCallback) {
-    wrapperSynthesizor.synthesizeWrappersForClasspath(synthesizedCallback);
-  }
-
-  private ProgramMethod generateCallbackMethod(
-      DexEncodedMethod originalMethod, DexProgramClass clazz) {
-    DexMethod methodToInstall =
-        methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
-    CfCode cfCode =
-        new APIConverterWrapperCfCodeProvider(
-                appView, originalMethod.getReference(), null, this, clazz.isInterface())
-            .generateCfCode();
-    DexEncodedMethod newMethod =
-        wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
-    newMethod.setCode(cfCode, appView);
-    if (originalMethod.isLibraryMethodOverride().isTrue()) {
-      newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
-    }
-    return new ProgramMethod(clazz, newMethod);
-  }
-
-  private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
     StringBuilder sb = new StringBuilder();
     sb.append("Tracked ").append(inner).append("desugared API conversions: ");
     for (DexMethod method : tracked) {
@@ -393,26 +263,7 @@
       sb.append(method);
     }
     appView.options().reporter.warning(new StringDiagnostic(sb.toString()));
-  }
-
-  public void reportInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
-    DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
-    StringDiagnostic diagnostic =
-        new StringDiagnostic(
-            "Invoke to "
-                + invokedMethod.holder
-                + "#"
-                + invokedMethod.name
-                + " may not work correctly at runtime (Cannot convert "
-                + debugString
-                + "type "
-                + desugaredType
-                + ").");
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      throw appView.options().reporter.fatalError(diagnostic);
-    } else {
-      appView.options().reporter.info(diagnostic);
-    }
+    tracked.clear();
   }
 
   public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
@@ -425,204 +276,296 @@
     return vivifiedType;
   }
 
-  public void registerWrappersForLibraryInvokeIfRequired(DexMethod invokedMethod) {
-    if (!shouldRewriteInvoke(invokedMethod)) {
-      return;
+  private static DexType invalidType(
+      DexMethod invokedMethod,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      AppView<?> appView) {
+    DexMethod convertedMethod =
+        methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
+    if (invokedMethod.getReturnType() != convertedMethod.getReturnType()
+        && returnConversion == null) {
+      return invokedMethod.getReturnType();
     }
-    if (trackedAPIs != null) {
-      trackedAPIs.add(invokedMethod);
-    }
-    DexType returnType = invokedMethod.proto.returnType;
-    if (appView.rewritePrefix.hasRewrittenType(returnType, appView) && canConvert(returnType)) {
-      registerConversionWrappers(returnType);
-    }
-    for (DexType argType : invokedMethod.proto.parameters.values) {
-      if (appView.rewritePrefix.hasRewrittenType(argType, appView) && canConvert(argType)) {
-        registerConversionWrappers(argType);
+    for (int i = 0; i < invokedMethod.getArity(); i++) {
+      if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i)
+          && parameterConversions[i] == null) {
+        return invokedMethod.getParameter(i);
       }
     }
+    return null;
   }
 
-  private void rewriteLibraryInvoke(
-      IRCode code,
-      InvokeMethod invokeMethod,
-      InstructionListIterator iterator,
-      ListIterator<BasicBlock> blockIterator) {
-    DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-    boolean invalidConversion = false;
-    if (trackedAPIs != null) {
-      trackedAPIs.add(invokedMethod);
+  public static DexMethod getConvertedAPI(
+      DexMethod invokedMethod,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      AppView<?> appView) {
+    DexType newReturnType =
+        returnConversion != null ? returnConversion.getParameter(0) : invokedMethod.getReturnType();
+    DexType[] newParameterTypes = new DexType[parameterConversions.length];
+    for (int i = 0; i < parameterConversions.length; i++) {
+      newParameterTypes[i] =
+          parameterConversions[i] != null
+              ? parameterConversions[i].getReturnType()
+              : invokedMethod.getParameter(i);
     }
+    DexMethod convertedAPI =
+        appView
+            .dexItemFactory()
+            .createMethod(
+                invokedMethod.holder,
+                appView.dexItemFactory().createProto(newReturnType, newParameterTypes),
+                invokedMethod.name);
+    assert convertedAPI
+            == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView)
+        || invalidType(invokedMethod, returnConversion, parameterConversions, appView) != null;
+    return convertedAPI;
+  }
 
-    // Create return conversion if required.
-    Instruction returnConversion = null;
-    DexType newReturnType;
+  private DexMethod computeReturnConversion(
+      DexMethod invokedMethod, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     DexType returnType = invokedMethod.proto.returnType;
-    if (appView.rewritePrefix.hasRewrittenType(returnType, appView)) {
-      if (canConvert(returnType)) {
-        newReturnType = vivifiedTypeFor(returnType, appView);
-        // Return conversion added only if return value is used.
-        if (invokeMethod.outValue() != null
-            && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
-                > 0) {
-          returnConversion =
-              createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType);
-        }
-      } else {
-        reportInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return ");
-        invalidConversion = true;
-        newReturnType = returnType;
-      }
-    } else {
-      newReturnType = returnType;
+    if (wrapperSynthesizor.shouldConvert(returnType, invokedMethod)) {
+      DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView);
+      return wrapperSynthesizor.ensureConversionMethod(
+          returnType, newReturnType, returnType, eventConsumer);
     }
+    return null;
+  }
 
-    // Create parameter conversions if required.
-    List<Instruction> parameterConversions = new ArrayList<>();
-    List<Value> newInValues = new ArrayList<>();
-    if (invokeMethod.isInvokeMethodWithReceiver()) {
-      assert !appView.rewritePrefix.hasRewrittenType(invokedMethod.holder, appView);
-      newInValues.add(invokeMethod.asInvokeMethodWithReceiver().getReceiver());
-    }
-    int receiverShift = BooleanUtils.intValue(invokeMethod.isInvokeMethodWithReceiver());
+  private DexMethod[] computeParameterConversions(
+      DexMethod invokedMethod, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
     DexType[] parameters = invokedMethod.proto.parameters.values;
-    DexType[] newParameters = parameters.clone();
     for (int i = 0; i < parameters.length; i++) {
       DexType argType = parameters[i];
-      if (appView.rewritePrefix.hasRewrittenType(argType, appView)) {
-        if (canConvert(argType)) {
-          DexType argVivifiedType = vivifiedTypeFor(argType, appView);
-          Value inValue = invokeMethod.inValues().get(i + receiverShift);
-          newParameters[i] = argVivifiedType;
-          parameterConversions.add(
-              createParameterConversion(code, argType, argVivifiedType, inValue));
-          newInValues.add(parameterConversions.get(parameterConversions.size() - 1).outValue());
-        } else {
-          reportInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter ");
-          invalidConversion = true;
-          newInValues.add(invokeMethod.inValues().get(i + receiverShift));
-        }
-      } else {
-        newInValues.add(invokeMethod.inValues().get(i + receiverShift));
+      if (wrapperSynthesizor.shouldConvert(argType, invokedMethod)) {
+        DexType argVivifiedType = vivifiedTypeFor(argType, appView);
+        parameterConversions[i] =
+            wrapperSynthesizor.ensureConversionMethod(
+                argType, argType, argVivifiedType, eventConsumer);
       }
     }
+    return parameterConversions;
+  }
 
-    // Patch the invoke with new types and new inValues.
-    DexProto newProto = factory.createProto(newReturnType, newParameters);
-    DexMethod newDexMethod =
-        factory.createMethod(invokedMethod.holder, newProto, invokedMethod.name);
-    Invoke newInvokeMethod =
-        Invoke.create(
-            invokeMethod.getType(),
-            newDexMethod,
-            newDexMethod.proto,
-            invokeMethod.outValue(),
-            newInValues);
-    assert newDexMethod
-            == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView)
-        || invalidConversion;
-
-    // Insert and reschedule all instructions.
-    iterator.previous();
-    for (Instruction parameterConversion : parameterConversions) {
-      parameterConversion.setPosition(invokeMethod.getPosition());
-      iterator.add(parameterConversion);
+  private Collection<CfInstruction> rewriteLibraryInvoke(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
+    DexMethod invokedMethod = invoke.getMethod();
+    if (trackedAPIs != null) {
+      trackedAPIs.add(invokedMethod);
     }
-    assert iterator.peekNext() == invokeMethod;
-    iterator.next();
-    iterator.replaceCurrentInstruction(newInvokeMethod);
+    if (shouldOutlineAPIConversion(invoke, context)) {
+      DexMethod outlinedAPIConversion =
+          createOutlinedAPIConversion(invoke, methodProcessingContext, eventConsumer);
+      return Collections.singletonList(
+          new CfInvoke(Opcodes.INVOKESTATIC, outlinedAPIConversion, false));
+    }
+    return rewriteLibraryInvokeToInlineAPIConversion(
+        invoke, methodProcessingContext, localStackAllocator, eventConsumer);
+  }
+
+  // If the option is set, we try to outline API conversions as much as possible to reduce the
+  // number
+  // of soft verification failures. We cannot outline API conversions through super invokes, to
+  // instance initializers and to non public methods.
+  private boolean shouldOutlineAPIConversion(CfInvoke invoke, ProgramMethod context) {
+    if (invoke.isInvokeSuper(context.getHolderType())) {
+      return false;
+    }
+    if (invoke.getMethod().isInstanceInitializer(appView.dexItemFactory())) {
+      return false;
+    }
+    DexClassAndMethod methodForDesugaring =
+        getMethodForDesugaring(invoke.getMethod(), false, invoke.isInterface(), context);
+    assert methodForDesugaring != null;
+    return methodForDesugaring.getAccessFlags().isPublic();
+  }
+
+  private Collection<CfInstruction> rewriteLibraryInvokeToInlineAPIConversion(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+
+    DexMethod invokedMethod = invoke.getMethod();
+    DexMethod returnConversion = computeReturnConversion(invokedMethod, eventConsumer);
+    DexMethod[] parameterConversions = computeParameterConversions(invokedMethod, eventConsumer);
+
+    // If only the last 2 parameters require conversion, we do everything inlined.
+    // If other parameters require conversion, we outline the parameter conversion but keep the API
+    // call inlined.
+    // The returned value is always converted inlined.
+    boolean requireOutlinedParameterConversion = false;
+    for (int i = 0; i < parameterConversions.length - 2; i++) {
+      requireOutlinedParameterConversion |= parameterConversions[i] != null;
+    }
+
+    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
+    if (requireOutlinedParameterConversion) {
+      addOutlineParameterConversionInstructions(
+          parameterConversions,
+          cfInstructions,
+          methodProcessingContext,
+          invokedMethod,
+          localStackAllocator,
+          eventConsumer);
+    } else {
+      addInlineParameterConversionInstructions(parameterConversions, cfInstructions);
+    }
+
+    DexMethod convertedMethod =
+        getConvertedAPI(invokedMethod, returnConversion, parameterConversions, appView);
+    cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface()));
+
     if (returnConversion != null) {
-      returnConversion.setPosition(invokeMethod.getPosition());
-      iterator.add(returnConversion);
+      cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
     }
 
-    // If the invoke is in a try-catch, since all conversions can throw, the basic block needs
-    // to be split in between each invoke...
-    if (newInvokeMethod.getBlock().hasCatchHandlers()) {
-      splitIfCatchHandlers(code, newInvokeMethod.getBlock(), blockIterator);
-    }
+    return cfInstructions;
   }
 
-  private void splitIfCatchHandlers(
-      IRCode code,
-      BasicBlock blockWithIncorrectThrowingInstructions,
-      ListIterator<BasicBlock> blockIterator) {
-    InstructionListIterator instructionsIterator =
-        blockWithIncorrectThrowingInstructions.listIterator(code);
-    BasicBlock currentBlock = blockWithIncorrectThrowingInstructions;
-    while (currentBlock != null && instructionsIterator.hasNext()) {
-      Instruction throwingInstruction =
-          instructionsIterator.nextUntil(Instruction::instructionTypeCanThrow);
-      BasicBlock nextBlock;
-      if (throwingInstruction != null) {
-        nextBlock = instructionsIterator.split(code, blockIterator);
-        // Back up to before the split before inserting catch handlers.
-        blockIterator.previous();
-        nextBlock.copyCatchHandlers(code, blockIterator, currentBlock, appView.options());
-        BasicBlock b = blockIterator.next();
-        assert b == nextBlock;
-        // Switch iteration to the split block.
-        instructionsIterator = nextBlock.listIterator(code);
-        currentBlock = nextBlock;
+  // The parameters are converted and returned in an array of converted parameters. The parameter
+  // array then needs to be unwrapped at the call site.
+  private void addOutlineParameterConversionInstructions(
+      DexMethod[] parameterConversions,
+      ArrayList<CfInstruction> cfInstructions,
+      MethodProcessingContext methodProcessingContext,
+      DexMethod invokedMethod,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    localStackAllocator.allocateLocalStack(4);
+    DexProto newProto =
+        appView
+            .dexItemFactory()
+            .createProto(
+                appView.dexItemFactory().objectArrayType, invokedMethod.getParameters().values);
+    ProgramMethod parameterConversion =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                SyntheticKind.API_CONVERSION_PARAMETERS,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(newProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            methodSignature ->
+                                computeParameterConversionCfCode(
+                                    methodSignature.holder, invokedMethod, parameterConversions)));
+    eventConsumer.acceptAPIConversion(parameterConversion);
+    cfInstructions.add(
+        new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false));
+    for (int i = 0; i < parameterConversions.length; i++) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
+      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
+      DexType parameterType =
+          parameterConversions[i] != null
+              ? parameterConversions[i].getReturnType()
+              : invokedMethod.getParameter(i);
+      cfInstructions.add(new CfArrayLoad(MemberType.OBJECT));
+      if (parameterType.isPrimitiveType()) {
+        cfInstructions.add(new CfCheckCast(factory.getBoxedForPrimitiveType(parameterType)));
+        DexMethod method = appView.dexItemFactory().getUnboxPrimitiveMethod(parameterType);
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
       } else {
-        assert !instructionsIterator.hasNext();
-        instructionsIterator = null;
-        currentBlock = null;
+        cfInstructions.add(new CfCheckCast(parameterType));
       }
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+    }
+    cfInstructions.add(new CfStackInstruction(Opcode.Pop));
+  }
+
+  private CfCode computeParameterConversionCfCode(
+      DexType holder, DexMethod invokedMethod, DexMethod[] parameterConversions) {
+    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
+    cfInstructions.add(new CfConstNumber(parameterConversions.length, ValueType.INT));
+    cfInstructions.add(new CfNewArray(factory.objectArrayType));
+    int stackIndex = 0;
+    for (int i = 0; i < invokedMethod.getArity(); i++) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
+      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
+      DexType param = invokedMethod.getParameter(i);
+      cfInstructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
+      if (parameterConversions[i] != null) {
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+      }
+      if (param.isPrimitiveType()) {
+        DexMethod method = appView.dexItemFactory().getBoxPrimitiveMethod(param);
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+      }
+      cfInstructions.add(new CfArrayStore(MemberType.OBJECT));
+      if (param == appView.dexItemFactory().longType
+          || param == appView.dexItemFactory().doubleType) {
+        stackIndex++;
+      }
+      stackIndex++;
+    }
+    cfInstructions.add(new CfReturn(ValueType.OBJECT));
+    return new CfCode(
+        holder,
+        invokedMethod.getParameters().size() + 4,
+        invokedMethod.getParameters().size(),
+        cfInstructions);
+  }
+
+  private void addInlineParameterConversionInstructions(
+      DexMethod[] parameterConversions, ArrayList<CfInstruction> cfInstructions) {
+    if (parameterConversions.length > 0
+        && parameterConversions[parameterConversions.length - 1] != null) {
+      cfInstructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 1], false));
+    }
+    if (parameterConversions.length > 1
+        && parameterConversions[parameterConversions.length - 2] != null) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+      cfInstructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 2], false));
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
     }
   }
 
-  private Instruction createParameterConversion(
-      IRCode code, DexType argType, DexType argVivifiedType, Value inValue) {
-    DexMethod conversionMethod = ensureConversionMethod(argType, argType, argVivifiedType);
-    // The value is null only if the input is null.
-    Value convertedValue =
-        createConversionValue(code, inValue.getType().nullability(), argVivifiedType, null);
-    return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
-  }
-
-  private Instruction createReturnConversionAndReplaceUses(
-      IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
-    DexMethod conversionMethod = ensureConversionMethod(returnType, returnVivifiedType, returnType);
-    Value outValue = invokeMethod.outValue();
-    Value convertedValue =
-        createConversionValue(code, Nullability.maybeNull(), returnType, outValue.getLocalInfo());
-    outValue.replaceUsers(convertedValue);
-    // The only user of out value is now the new invoke static, so no type propagation is required.
-    outValue.setType(
-        TypeElement.fromDexType(returnVivifiedType, outValue.getType().nullability(), appView));
-    return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(outValue));
-  }
-
-  private void registerConversionWrappers(DexType type) {
-    if (appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type) == null) {
-      wrapperSynthesizor.registerWrapper(type);
-    }
-  }
-
-  public DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
-    // ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
-    // But everything is going to be rewritten, so we need to use vivifiedType and type".
-    DexType conversionHolder =
-        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type);
-    if (conversionHolder == null) {
-      conversionHolder =
-          type == srcType
-              ? wrapperSynthesizor.ensureTypeWrapper(type)
-              : wrapperSynthesizor.ensureVivifiedTypeWrapper(type);
-    }
-    assert conversionHolder != null;
-    return factory.createMethod(
-        conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
-  }
-
-  private Value createConversionValue(
-      IRCode code, Nullability nullability, DexType valueType, DebugLocalInfo localInfo) {
-    return code.createValue(TypeElement.fromDexType(valueType, nullability, appView), localInfo);
-  }
-
-  public boolean canConvert(DexType type) {
-    return appView.options().desugaredLibraryConfiguration.getCustomConversions().containsKey(type)
-        || wrapperSynthesizor.canGenerateWrapper(type);
+  private DexMethod createOutlinedAPIConversion(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    DexMethod invokedMethod = invoke.getMethod();
+    DexProto newProto =
+        invoke.isInvokeStatic()
+            ? invokedMethod.proto
+            : factory.prependTypeToProto(invokedMethod.getHolderType(), invokedMethod.getProto());
+    DexMethod returnConversion = computeReturnConversion(invokedMethod, eventConsumer);
+    DexMethod[] parameterConversions = computeParameterConversions(invokedMethod, eventConsumer);
+    ProgramMethod outline =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                SyntheticKind.API_CONVERSION,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(newProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setCode(
+                            methodSignature ->
+                                new APIConversionCfCodeProvider(
+                                        appView,
+                                        methodSignature.holder,
+                                        invoke,
+                                        returnConversion,
+                                        parameterConversions)
+                                    .generateCfCode()));
+    eventConsumer.acceptAPIConversion(outline);
+    return outline.getReference();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
new file mode 100644
index 0000000..7af64bb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverterEventConsumer.java
@@ -0,0 +1,23 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface DesugaredLibraryAPIConverterEventConsumer {
+
+  void acceptWrapperProgramClass(DexProgramClass clazz);
+
+  void acceptWrapperClasspathClass(DexClasspathClass clazz);
+
+  void acceptAPIConversion(ProgramMethod method);
+
+  interface DesugaredLibraryAPIConverterPostProcessingEventConsumer {
+
+    void acceptAPIConversionCallback(ProgramMethod method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
index 43b26e1..a4c9b12 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
@@ -17,8 +17,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -198,7 +198,7 @@
         return NO_REWRITING;
       }
       // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
-      ResolutionResult resolutionResult =
+      MethodResolutionResult resolutionResult =
           appView.appInfoForDesugaring().resolveMethod(invokedMethod, isInterface);
       if (resolutionResult.isFailedResolution()) {
         return NO_REWRITING;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
index dac3079..64d3cc6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterPostProcessor.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.collect.Maps;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -44,16 +45,19 @@
 
   @Override
   public void postProcessingDesugaring(
-      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      Collection<DexProgramClass> programClasses,
+      CfPostProcessingDesugaringEventConsumer eventConsumer,
+      ExecutorService executorService)
       throws ExecutionException {
     if (appView.options().isDesugaredLibraryCompilation()) {
       ensureEmulatedDispatchMethodsSynthesized(eventConsumer);
     } else {
-      ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer);
+      ensureInterfacesAndForwardingMethodsSynthesized(programClasses, eventConsumer);
     }
   }
 
   private void ensureInterfacesAndForwardingMethodsSynthesized(
+      Collection<DexProgramClass> programClasses,
       DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer) {
     assert !appView.options().isDesugaredLibraryCompilation();
     Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap();
@@ -61,7 +65,7 @@
       map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
       map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
     }
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
+    for (DexProgramClass clazz : programClasses) {
       if (clazz.superType == null) {
         assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
         continue;
@@ -110,6 +114,9 @@
     // methods.
     // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
     // applies up to 24.
+    if (appView.isAlreadyLibraryDesugared(clazz)) {
+      return;
+    }
     for (DexClassAndMethod method : methods) {
       DexClass newInterface =
           syntheticHelper.ensureEmulatedInterfaceDispatchMethod(method, eventConsumer);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
index 1f8322f..02eb59b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -150,6 +150,7 @@
               .setCode(
                   methodSig ->
                       new EmulateInterfaceSyntheticCfCodeProvider(
+                              methodSig.getHolderType(),
                               emulatedDispatchMethod.getHolderType(),
                               desugarMethod,
                               itfMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
index 6c1e889..2605672 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryWrapperSynthesizer.java
@@ -34,16 +34,16 @@
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
-import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -93,19 +93,13 @@
 public class DesugaredLibraryWrapperSynthesizer {
 
   private final AppView<?> appView;
-  private final Set<DexType> wrappersToGenerate = Sets.newConcurrentHashSet();
-  // The invalidWrappers are wrappers with incorrect behavior because of final methods that could
-  // not be overridden. Such wrappers are awful because the runtime behavior is undefined and does
-  // not raise explicit errors. So we register them here and conversion methods for such wrappers
-  // raise a runtime exception instead of generating the wrapper.
-  private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
   private final DexItemFactory factory;
-  private final DesugaredLibraryAPIConverter converter;
+  private final ConcurrentHashMap<DexType, List<DexEncodedMethod>> allImplementedMethodsCache =
+      new ConcurrentHashMap<>();
 
-  DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
+  DesugaredLibraryWrapperSynthesizer(AppView<?> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
-    this.converter = converter;
   }
 
   public boolean isSyntheticWrapper(DexType type) {
@@ -113,21 +107,72 @@
         || appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.VIVIFIED_WRAPPER);
   }
 
-  boolean canGenerateWrapper(DexType type) {
+  public boolean shouldConvert(DexType type, DexMethod method) {
+    if (!appView.rewritePrefix.hasRewrittenType(type, appView)) {
+      return false;
+    }
+    if (canConvert(type)) {
+      return true;
+    }
+    reportInvalidInvoke(type, method);
+    return false;
+  }
+
+  public DexMethod ensureConversionMethod(
+      DexType type,
+      DexType srcType,
+      DexType destType,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    // ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
+    // But everything is going to be rewritten, so we need to use vivifiedType and type".
+    DexType conversionHolder =
+        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type);
+    if (conversionHolder == null) {
+      conversionHolder =
+          type == srcType
+              ? ensureTypeWrapper(type, eventConsumer)
+              : ensureVivifiedTypeWrapper(type, eventConsumer);
+    }
+    assert conversionHolder != null;
+    return factory.createMethod(
+        conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
+  }
+
+  private boolean canConvert(DexType type) {
+    return appView.options().desugaredLibraryConfiguration.getCustomConversions().containsKey(type)
+        || canGenerateWrapper(type);
+  }
+
+  private void reportInvalidInvoke(DexType type, DexMethod invokedMethod) {
+    DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
+    StringDiagnostic diagnostic =
+        new StringDiagnostic(
+            "Invoke to "
+                + invokedMethod.holder
+                + "#"
+                + invokedMethod.name
+                + " may not work correctly at runtime (Cannot convert type "
+                + desugaredType
+                + ").");
+    if (appView.options().isDesugaredLibraryCompilation()) {
+      throw appView.options().reporter.fatalError(diagnostic);
+    } else {
+      appView.options().reporter.info(diagnostic);
+    }
+  }
+
+  private boolean canGenerateWrapper(DexType type) {
     return appView.options().desugaredLibraryConfiguration.getWrapperConversions().contains(type);
   }
 
-  DexType ensureTypeWrapper(DexType type) {
-    return ensureWrappers(type).getWrapper().type;
+  private DexType ensureTypeWrapper(
+      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    return ensureWrappers(type, eventConsumer).getWrapper().type;
   }
 
-  DexType ensureVivifiedTypeWrapper(DexType type) {
-    return ensureWrappers(type).getVivifiedWrapper().type;
-  }
-
-  public void registerWrapper(DexType type) {
-    wrappersToGenerate.add(type);
-    assert getValidClassToWrap(type) != null;
+  private DexType ensureVivifiedTypeWrapper(
+      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
+    return ensureWrappers(type, eventConsumer).getVivifiedWrapper().type;
   }
 
   private DexClass getValidClassToWrap(DexType type) {
@@ -161,13 +206,17 @@
     }
   }
 
-  private Wrappers ensureWrappers(DexType type) {
+  private Wrappers ensureWrappers(
+      DexType type, DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     assert canGenerateWrapper(type) : type;
     DexClass dexClass = getValidClassToWrap(type);
-    return ensureWrappers(dexClass, ignored -> {});
+    return ensureWrappers(dexClass, ignored -> {}, eventConsumer);
   }
 
-  private Wrappers ensureWrappers(DexClass context, Consumer<DexClasspathClass> creationCallback) {
+  private Wrappers ensureWrappers(
+      DexClass context,
+      Consumer<DexClasspathClass> creationCallback,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     DexType type = context.type;
     DexClass wrapper;
     DexClass vivifiedWrapper;
@@ -180,15 +229,20 @@
               vivifiedTypeFor(type),
               type,
               programContext,
-              wrapperField -> synthesizeVirtualMethodsForTypeWrapper(programContext, wrapperField));
+              eventConsumer,
+              wrapperField ->
+                  synthesizeVirtualMethodsForTypeWrapper(
+                      programContext, wrapperField, eventConsumer));
       vivifiedWrapper =
           ensureProgramWrapper(
               SyntheticKind.VIVIFIED_WRAPPER,
               type,
               vivifiedTypeFor(type),
               programContext,
+              eventConsumer,
               wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(programContext, wrapperField));
+                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
+                      programContext, wrapperField, eventConsumer));
       DexField wrapperField = getWrapperUniqueField(wrapper);
       DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
       ensureProgramConversionMethod(
@@ -205,7 +259,9 @@
               type,
               classpathOrLibraryContext,
               creationCallback,
-              wrapperField -> synthesizeVirtualMethodsForTypeWrapper(context, wrapperField));
+              eventConsumer,
+              wrapperField ->
+                  synthesizeVirtualMethodsForTypeWrapper(context, wrapperField, eventConsumer));
       vivifiedWrapper =
           ensureClasspathWrapper(
               SyntheticKind.VIVIFIED_WRAPPER,
@@ -213,8 +269,10 @@
               vivifiedTypeFor(type),
               classpathOrLibraryContext,
               creationCallback,
+              eventConsumer,
               wrapperField ->
-                  synthesizeVirtualMethodsForVivifiedTypeWrapper(context, wrapperField));
+                  synthesizeVirtualMethodsForVivifiedTypeWrapper(
+                      context, wrapperField, eventConsumer));
       DexField wrapperField = getWrapperUniqueField(wrapper);
       DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
       ensureClasspathConversionMethod(
@@ -242,6 +300,7 @@
       DexType wrappingType,
       DexType wrappedType,
       DexProgramClass programContext,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
       Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
     return appView
         .getSyntheticItems()
@@ -252,9 +311,13 @@
             builder -> buildWrapper(wrappingType, wrappedType, programContext, builder),
             // The creation of virtual methods may require new wrappers, this needs to happen
             // once the wrapper is created to avoid infinite recursion.
-            wrapper ->
-                wrapper.setVirtualMethods(
-                    virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper))));
+            wrapper -> {
+              if (eventConsumer != null) {
+                eventConsumer.acceptWrapperProgramClass(wrapper);
+              }
+              wrapper.setVirtualMethods(
+                  virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
+            });
   }
 
   private DexClasspathClass ensureClasspathWrapper(
@@ -263,6 +326,7 @@
       DexType wrappedType,
       ClasspathOrLibraryClass classpathOrLibraryContext,
       Consumer<DexClasspathClass> creationCallback,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
       Function<DexEncodedField, DexEncodedMethod[]> virtualMethodProvider) {
     return appView
         .getSyntheticItems()
@@ -276,6 +340,9 @@
             // The creation of virtual methods may require new wrappers, this needs to happen
             // once the wrapper is created to avoid infinite recursion.
             wrapper -> {
+              if (eventConsumer != null) {
+                eventConsumer.acceptWrapperClasspathClass(wrapper);
+              }
               wrapper.setVirtualMethods(
                   virtualMethodProvider.apply(getWrapperUniqueEncodedField(wrapper)));
               creationCallback.accept(wrapper);
@@ -297,8 +364,7 @@
             appView,
             ignored -> {},
             methodBuilder ->
-                buildConversionMethod(
-                    methodBuilder, wrapperField, reverseWrapperField, context.type));
+                buildConversionMethod(methodBuilder, wrapperField, reverseWrapperField, context));
   }
 
   private DexClassAndMethod ensureClasspathConversionMethod(
@@ -317,22 +383,26 @@
             ignored -> {},
             methodBuilder ->
                 buildConversionMethod(
-                    methodBuilder, wrapperField, reverseWrapperField, context.getType()));
+                    methodBuilder, wrapperField, reverseWrapperField, context.asDexClass()));
+  }
+
+  private boolean isInvalidWrapper(DexClass clazz) {
+    return Iterables.any(allImplementedMethods(clazz), DexEncodedMethod::isFinal);
   }
 
   private void buildConversionMethod(
       SyntheticMethodBuilder methodBuilder,
       DexField wrapperField,
       DexField reverseWrapperField,
-      DexType reportingType) {
+      DexClass context) {
     CfCode cfCode;
-    if (invalidWrappers.contains(wrapperField.holder)) {
+    if (isInvalidWrapper(context)) {
       cfCode =
           new APIConverterThrowRuntimeExceptionCfCodeProvider(
                   appView,
                   factory.createString(
                       "Unsupported conversion for "
-                          + reportingType
+                          + context.type
                           + ". See compilation time warnings for more details."),
                   wrapperField.holder)
               .generateCfCode();
@@ -382,7 +452,9 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
-      DexClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass,
+      DexEncodedField wrapperField,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only types in their signature, but each method the wrapper forwards
@@ -415,13 +487,17 @@
               dexEncodedMethod.getReference().name);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
-        invalidWrappers.add(wrapperField.getHolderType());
         finalMethods.add(dexEncodedMethod.getReference());
         continue;
       } else {
         cfCode =
             new APIConverterVivifiedWrapperCfCodeProvider(
-                    appView, methodToInstall, wrapperField.getReference(), converter, isInterface)
+                    appView,
+                    methodToInstall,
+                    wrapperField.getReference(),
+                    this,
+                    isInterface,
+                    eventConsumer)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -432,7 +508,9 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
-      DexClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass,
+      DexEncodedField wrapperField,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only vivified types in their signature, but each method the wrapper
@@ -455,7 +533,6 @@
               dexEncodedMethod.getReference(), wrapperField.getHolderType(), appView);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
-        invalidWrappers.add(wrapperField.getHolderType());
         finalMethods.add(dexEncodedMethod.getReference());
         continue;
       } else {
@@ -464,8 +541,9 @@
                     appView,
                     dexEncodedMethod.getReference(),
                     wrapperField.getReference(),
-                    converter,
-                    isInterface)
+                    this,
+                    isInterface,
+                    eventConsumer)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -522,7 +600,12 @@
         true);
   }
 
-  private List<DexEncodedMethod> allImplementedMethods(DexClass libraryClass) {
+  private List<DexEncodedMethod> allImplementedMethods(DexClass clazz) {
+    return allImplementedMethodsCache.computeIfAbsent(
+        clazz.type, type -> internalAllImplementedMethods(clazz));
+  }
+
+  private List<DexEncodedMethod> internalAllImplementedMethods(DexClass libraryClass) {
     LinkedList<DexClass> workList = new LinkedList<>();
     List<DexEncodedMethod> implementedMethods = new ArrayList<>();
     workList.add(libraryClass);
@@ -574,7 +657,7 @@
         field, fieldAccessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
   }
 
-  void finalizeWrappersForL8() {
+  void ensureWrappersForL8(DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
     DesugaredLibraryConfiguration conf = appView.options().desugaredLibraryConfiguration;
     for (DexType type : conf.getWrapperConversions()) {
       assert !conf.getCustomConversions().containsKey(type);
@@ -582,24 +665,7 @@
       // In broken set-ups we can end up having a json files containing wrappers of non desugared
       // classes. Such wrappers are not required since the class won't be rewritten.
       if (validClassToWrap.isProgramClass()) {
-        ensureWrappers(validClassToWrap, ignored -> {});
-      }
-    }
-  }
-
-  void synthesizeWrappersForClasspath(Consumer<DexClasspathClass> synthesizedCallback) {
-    BooleanBox changed = new BooleanBox(true);
-    while (changed.get()) {
-      changed.set(false);
-      Set<DexType> copy = new HashSet<>(wrappersToGenerate);
-      for (DexType type : copy) {
-        DexClass validClassToWrap = getValidClassToWrap(type);
-        ensureWrappers(
-            validClassToWrap,
-            classpathWrapper -> {
-              changed.set(true);
-              synthesizedCallback.accept(classpathWrapper);
-            });
+        ensureWrappers(validClassToWrap, ignored -> {}, eventConsumer);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 14e5fea..90a7b35 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -27,8 +27,8 @@
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -309,8 +309,8 @@
       return appView.appInfo().definitionForDesugarDependency(directSubClass, type);
     }
 
-    public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
-      rewriter.warnMissingInterface(closestProgramSubClass, closestProgramSubClass, missingType);
+    public void reportMissingType(DexType missingType, InterfaceDesugaringSyntheticHelper helper) {
+      helper.warnMissingInterface(closestProgramSubClass, closestProgramSubClass, missingType);
     }
   }
 
@@ -334,14 +334,14 @@
     }
 
     @Override
-    public void reportMissingType(DexType missingType, InterfaceMethodRewriter rewriter) {
+    public void reportMissingType(DexType missingType, InterfaceDesugaringSyntheticHelper helper) {
       // Ignore missing types in the library.
     }
   }
 
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
-  private final InterfaceMethodRewriter rewriter;
+  private final InterfaceDesugaringSyntheticHelper helper;
   private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
   private final boolean needsLibraryInfo;
 
@@ -358,10 +358,10 @@
   private final Map<DexProgramClass, ProgramMethodSet> newSyntheticMethods =
       new ConcurrentHashMap<>();
 
-  ClassProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
+  ClassProcessor(AppView<?> appView) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
-    this.rewriter = rewriter;
+    this.helper = new InterfaceDesugaringSyntheticHelper(appView);
     needsLibraryInfo =
         !appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
             || !appView
@@ -402,9 +402,9 @@
   private SignaturesInfo computeInterfaceInfo(DexClass iface, SignaturesInfo interfaceInfo) {
     assert iface.isInterface();
     assert iface.superType == dexItemFactory.objectType;
-    assert !rewriter.isEmulatedInterface(iface.type);
+    assert !helper.isEmulatedInterface(iface.type);
     // Add non-library default methods as well as those for desugared library classes.
-    if (!iface.isLibraryClass() || (needsLibraryInfo() && rewriter.isInDesugaredLibrary(iface))) {
+    if (!iface.isLibraryClass() || (needsLibraryInfo() && helper.isInDesugaredLibrary(iface))) {
       MethodSignatures signatures = getDefaultMethods(iface);
       interfaceInfo = interfaceInfo.withSignatures(signatures);
     }
@@ -415,7 +415,7 @@
       DexClass iface, SignaturesInfo interfaceInfo) {
     assert iface.isInterface();
     assert iface.superType == dexItemFactory.objectType;
-    assert rewriter.isEmulatedInterface(iface.type);
+    assert helper.isEmulatedInterface(iface.type);
     assert needsLibraryInfo();
     MethodSignatures signatures = getDefaultMethods(iface);
     EmulatedInterfaceInfo emulatedInterfaceInfo =
@@ -548,7 +548,7 @@
               extraInterfaceSignatures.put(
                   type,
                   new GenericSignature.ClassTypeSignature(
-                      rewriter.getEmulatedInterface(type), signature.typeArguments()));
+                      helper.getEmulatedInterface(type), signature.typeArguments()));
             }
             collectEmulatedInterfacesWithPropagatedTypeArguments(
                 type, signature.typeArguments(), emulatesInterfaces, extraInterfaceSignatures);
@@ -558,8 +558,7 @@
           (type) -> {
             if (emulatesInterfaces.contains(type)) {
               extraInterfaceSignatures.put(
-                  type,
-                  new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(type)));
+                  type, new GenericSignature.ClassTypeSignature(helper.getEmulatedInterface(type)));
             }
             collectEmulatedInterfacesWithPropagatedTypeArguments(
                 type, null, emulatesInterfaces, extraInterfaceSignatures);
@@ -586,7 +585,7 @@
               extraInterfaceSignatures.put(
                   iface,
                   new GenericSignature.ClassTypeSignature(
-                      rewriter.getEmulatedInterface(iface), signature));
+                      helper.getEmulatedInterface(iface), signature));
             }
             collectEmulatedInterfacesWithPropagatedTypeArguments(
                 iface, signature, emulatesInterfaces, extraInterfaceSignatures);
@@ -598,7 +597,7 @@
             if (emulatesInterfaces.contains(iface)) {
               extraInterfaceSignatures.put(
                   iface,
-                  new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(iface)));
+                  new GenericSignature.ClassTypeSignature(helper.getEmulatedInterface(iface)));
             }
             collectEmulatedInterfacesWithPropagatedTypeArguments(
                 iface, null, emulatesInterfaces, extraInterfaceSignatures);
@@ -611,7 +610,8 @@
       DexClass clazz, EmulatedInterfaceInfo emulatedInterfaceInfo) {
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
     for (Wrapper<DexMethod> signature : emulatedInterfaceInfo.signatures.signatures) {
-      ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(signature.get(), clazz);
+      MethodResolutionResult resolutionResult =
+          appInfo.resolveMethodOnClass(signature.get(), clazz);
       if (resolutionResult.isFailedResolution()) {
         return true;
       }
@@ -650,7 +650,7 @@
   private void resolveForwardForSignature(
       DexClass clazz, DexMethod method, Consumer<DexClassAndMethod> addForward) {
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-    ResolutionResult resolutionResult = appInfo.resolveMethodOn(clazz, method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(clazz, method);
     if (resolutionResult.isFailedResolution()
         || resolutionResult.asSuccessfulMemberResolutionResult().getResolvedMember().isStatic()) {
       // When doing resolution we may find a static or private targets and bubble up the failed
@@ -717,7 +717,7 @@
       // Here we use step-3 of resolution to find a maximally specific default interface method.
       DexClassAndMethod result =
           appInfo.lookupMaximallySpecificMethod(libraryMethod.getHolder(), method);
-      if (result != null && rewriter.isEmulatedInterface(result.getHolderType())) {
+      if (result != null && helper.isEmulatedInterface(result.getHolderType())) {
         addForward.accept(result);
       }
     }
@@ -731,9 +731,7 @@
   }
 
   private boolean dontRewrite(DexClassAndMethod method) {
-    return needsLibraryInfo()
-        && method.getHolder().isLibraryClass()
-        && rewriter.dontRewrite(method);
+    return needsLibraryInfo() && method.getHolder().isLibraryClass() && helper.dontRewrite(method);
   }
 
   // Construction of actual forwarding methods.
@@ -818,7 +816,7 @@
     // In desugared library, emulated interface methods can be overridden by retarget lib members.
     DexMethod forwardMethod =
         target.getHolder().isInterface()
-            ? rewriter.ensureDefaultAsMethodOfCompanionClassStub(target).getReference()
+            ? helper.ensureDefaultAsMethodOfCompanionClassStub(target).getReference()
             : appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
@@ -835,7 +833,7 @@
     }
     DexClass clazz = context.definitionFor(type, appView);
     if (clazz == null) {
-      context.reportMissingType(type, rewriter);
+      context.reportMissingType(type, helper);
       return null;
     }
     return clazz;
@@ -935,7 +933,7 @@
     for (DexType superiface : iface.interfaces.values) {
       interfaceInfo = interfaceInfo.merge(visitInterfaceInfo(superiface, thisContext));
     }
-    return rewriter.isEmulatedInterface(iface.type)
+    return helper.isEmulatedInterface(iface.type)
         ? computeEmulatedInterfaceInfo(iface, interfaceInfo)
         : computeInterfaceInfo(iface, interfaceInfo);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index 1c29a09..c3fded8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.itf;
 
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -33,15 +31,12 @@
 public final class EmulatedInterfaceProcessor implements InterfaceDesugaringProcessor {
 
   private final AppView<?> appView;
-  private final InterfaceMethodRewriter rewriter;
-  private final Map<DexType, DexType> emulatedInterfaces;
+  private final InterfaceDesugaringSyntheticHelper helper;
   private final Map<DexType, List<DexType>> emulatedInterfacesHierarchy;
 
-  EmulatedInterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
+  EmulatedInterfaceProcessor(AppView<?> appView) {
     this.appView = appView;
-    this.rewriter = rewriter;
-    emulatedInterfaces =
-        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+    helper = new InterfaceDesugaringSyntheticHelper(appView);
     // Avoid the computation outside L8 since it is not needed.
     emulatedInterfacesHierarchy =
         appView.options().isDesugaredLibraryCompilation()
@@ -52,7 +47,7 @@
   private Map<DexType, List<DexType>> processEmulatedInterfaceHierarchy() {
     Map<DexType, List<DexType>> emulatedInterfacesHierarchy = new IdentityHashMap<>();
     Set<DexType> processed = Sets.newIdentityHashSet();
-    ArrayList<DexType> emulatedInterfacesSorted = new ArrayList<>(emulatedInterfaces.keySet());
+    ArrayList<DexType> emulatedInterfacesSorted = new ArrayList<>(helper.getEmulatedInterfaces());
     emulatedInterfacesSorted.sort(DexType::compareTo);
     for (DexType interfaceType : emulatedInterfacesSorted) {
       processEmulatedInterfaceHierarchy(interfaceType, processed, emulatedInterfacesHierarchy);
@@ -76,7 +71,7 @@
     LinkedList<DexType> workList = new LinkedList<>(Arrays.asList(theInterface.interfaces.values));
     while (!workList.isEmpty()) {
       DexType next = workList.removeLast();
-      if (emulatedInterfaces.containsKey(next)) {
+      if (helper.isEmulatedInterface(next)) {
         processEmulatedInterfaceHierarchy(next, processed, emulatedInterfacesHierarchy);
         emulatedInterfacesHierarchy.get(next).add(interfaceType);
         DexClass nextClass = appView.definitionFor(next);
@@ -89,7 +84,7 @@
 
   DexProgramClass ensureEmulateInterfaceLibrary(
       DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
-    assert rewriter.isEmulatedInterface(emulatedInterface.type);
+    assert helper.isEmulatedInterface(emulatedInterface.type);
     DexProgramClass emulateInterfaceClass =
         appView
             .getSyntheticItems()
@@ -108,7 +103,7 @@
                 ignored -> {});
     emulateInterfaceClass.forEachProgramMethod(eventConsumer::acceptEmulatedInterfaceMethod);
     assert emulateInterfaceClass.getType()
-        == InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType(
+        == InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType(
             emulatedInterface.type, appView.dexItemFactory());
     return emulateInterfaceClass;
   }
@@ -119,20 +114,23 @@
     DexMethod libraryMethod =
         method
             .getReference()
-            .withHolder(emulatedInterfaces.get(theInterface.type), appView.dexItemFactory());
+            .withHolder(helper.getEmulatedInterface(theInterface.type), appView.dexItemFactory());
     DexMethod companionMethod =
-        rewriter.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
+        helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
     List<Pair<DexType, DexMethod>> extraDispatchCases =
         getDispatchCases(method, theInterface, companionMethod);
-    DexMethod emulatedMethod = emulateInterfaceLibraryMethod(method, appView.dexItemFactory());
+    DexMethod emulatedMethod =
+        InterfaceDesugaringSyntheticHelper.emulateInterfaceLibraryMethod(
+            method, appView.dexItemFactory());
     methodBuilder
         .setName(emulatedMethod.getName())
         .setProto(emulatedMethod.getProto())
         .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
         .setCode(
-            theMethod ->
+            emulatedInterfaceMethod ->
                 new EmulateInterfaceSyntheticCfCodeProvider(
-                        theMethod.getHolderType(),
+                        emulatedInterfaceMethod.getHolderType(),
+                        method.getHolderType(),
                         companionMethod,
                         libraryMethod,
                         extraDispatchCases,
@@ -185,7 +183,7 @@
           extraDispatchCases.add(
               new Pair<>(
                   subInterfaceClass.type,
-                  InterfaceMethodRewriter.defaultAsMethodOfCompanionClass(
+                  InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
                       result.getReference(), appView.dexItemFactory())));
         }
       }
@@ -214,7 +212,7 @@
   public void process(
       DexProgramClass emulatedInterface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     if (!appView.options().isDesugaredLibraryCompilation()
-        || !rewriter.isEmulatedInterface(emulatedInterface.type)
+        || !helper.isEmulatedInterface(emulatedInterface.type)
         || appView.isAlreadyLibraryDesugared(emulatedInterface)) {
       return;
     }
@@ -233,7 +231,7 @@
   }
 
   private void warnMissingEmulatedInterfaces() {
-    for (DexType interfaceType : emulatedInterfaces.keySet()) {
+    for (DexType interfaceType : helper.getEmulatedInterfaces()) {
       DexClass theInterface = appView.definitionFor(interfaceType);
       if (theInterface == null) {
         warnMissingEmulatedInterface(interfaceType);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java
index 81ec904..a3e0164 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java
@@ -7,22 +7,22 @@
 public class InterfaceDesugaringForTesting {
 
   public static String getEmulateLibraryClassNameSuffix() {
-    return InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
+    return InterfaceDesugaringSyntheticHelper.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
   }
 
   public static String getCompanionClassNameSuffix() {
-    return InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+    return InterfaceDesugaringSyntheticHelper.COMPANION_CLASS_NAME_SUFFIX;
   }
 
   public static String getDefaultMethodPrefix() {
-    return InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
+    return InterfaceDesugaringSyntheticHelper.DEFAULT_METHOD_PREFIX;
   }
 
   public static String getPrivateMethodPrefix() {
-    return InterfaceMethodRewriter.PRIVATE_METHOD_PREFIX;
+    return InterfaceDesugaringSyntheticHelper.PRIVATE_METHOD_PREFIX;
   }
 
   public static String getCompanionClassDescriptor(String descriptor) {
-    return InterfaceMethodRewriter.getCompanionClassDescriptor(descriptor);
+    return InterfaceDesugaringSyntheticHelper.getCompanionClassDescriptor(descriptor);
   }
 }
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
new file mode 100644
index 0000000..ad5aa5e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -0,0 +1,358 @@
+// 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.desugar.itf;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+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.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.InvalidCode;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Pair;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+public class InterfaceDesugaringSyntheticHelper {
+
+  // Use InterfaceDesugaringForTesting for public accesses in tests.
+  static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
+  static final String COMPANION_CLASS_NAME_SUFFIX = "$-CC";
+  static final String DEFAULT_METHOD_PREFIX = "$default$";
+  static final String PRIVATE_METHOD_PREFIX = "$private$";
+
+  private final AppView<?> appView;
+  private final Map<DexType, DexType> emulatedInterfaces;
+  private final Predicate<DexType> shouldIgnoreFromReportsPredicate;
+
+  public InterfaceDesugaringSyntheticHelper(AppView<?> appView) {
+    this.appView = appView;
+    emulatedInterfaces =
+        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+
+    this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
+  }
+
+  boolean isEmulatedInterface(DexType itf) {
+    return emulatedInterfaces.containsKey(itf);
+  }
+
+  boolean isRewrittenEmulatedInterface(DexType itf) {
+    return emulatedInterfaces.containsValue(itf);
+  }
+
+  Set<DexType> getEmulatedInterfaces() {
+    return emulatedInterfaces.keySet();
+  }
+
+  DexType getEmulatedInterface(DexType type) {
+    return emulatedInterfaces.get(type);
+  }
+
+  boolean isInDesugaredLibrary(DexClass clazz) {
+    assert clazz.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
+    if (isEmulatedInterface(clazz.type)) {
+      return true;
+    }
+    return appView.rewritePrefix.hasRewrittenType(clazz.type, appView);
+  }
+
+  boolean dontRewrite(DexClassAndMethod method) {
+    for (Pair<DexType, DexString> dontRewrite :
+        appView.options().desugaredLibraryConfiguration.getDontRewriteInvocation()) {
+      if (method.getHolderType() == dontRewrite.getFirst()
+          && method.getName() == dontRewrite.getSecond()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  final boolean isCompatibleDefaultMethod(DexEncodedMethod method) {
+    assert !method.accessFlags.isConstructor();
+    assert !method.accessFlags.isStatic();
+
+    if (method.accessFlags.isAbstract()) {
+      return false;
+    }
+    if (method.accessFlags.isNative()) {
+      throw new Unimplemented("Native default interface methods are not yet supported.");
+    }
+    if (!method.accessFlags.isPublic()) {
+      // NOTE: even though the class is allowed to have non-public interface methods
+      // with code, for example private methods, all such methods we are aware of are
+      // created by the compiler for stateful lambdas and they must be converted into
+      // static methods by lambda desugaring by this time.
+      throw new Unimplemented("Non public default interface methods are not yet supported.");
+    }
+    return true;
+  }
+
+  public static DexMethod emulateInterfaceLibraryMethod(
+      DexClassAndMethod method, DexItemFactory factory) {
+    return factory.createMethod(
+        getEmulateLibraryInterfaceClassType(method.getHolderType(), factory),
+        factory.prependTypeToProto(method.getHolderType(), method.getProto()),
+        method.getName());
+  }
+
+  private static String getEmulateLibraryInterfaceClassDescriptor(String descriptor) {
+    return descriptor.substring(0, descriptor.length() - 1)
+        + EMULATE_LIBRARY_CLASS_NAME_SUFFIX
+        + ";";
+  }
+
+  public static DexType getEmulateLibraryInterfaceClassType(DexType type, DexItemFactory factory) {
+    assert type.isClassType();
+    String descriptor = type.descriptor.toString();
+    String elTypeDescriptor = getEmulateLibraryInterfaceClassDescriptor(descriptor);
+    return factory.createSynthesizedType(elTypeDescriptor);
+  }
+
+  public static String getCompanionClassDescriptor(String descriptor) {
+    return descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";";
+  }
+
+  // Gets the companion class for the interface `type`.
+  static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
+    assert type.isClassType();
+    String descriptor = type.descriptor.toString();
+    String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
+    return factory.createSynthesizedType(ccTypeDescriptor);
+  }
+
+  // Checks if `type` is a companion class.
+  public static boolean isCompanionClassType(DexType type) {
+    return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
+  }
+
+  public static boolean isEmulatedLibraryClassType(DexType type) {
+    return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
+  }
+
+  // Gets the interface class for a companion class `type`.
+  DexType getInterfaceClassType(DexType type) {
+    return getInterfaceClassType(type, appView.dexItemFactory());
+  }
+
+  // Gets the interface class for a companion class `type`.
+  public static DexType getInterfaceClassType(DexType type, DexItemFactory factory) {
+    assert isCompanionClassType(type);
+    String descriptor = type.descriptor.toString();
+    String interfaceTypeDescriptor =
+        descriptor.substring(0, descriptor.length() - 1 - COMPANION_CLASS_NAME_SUFFIX.length())
+            + ";";
+    return factory.createType(interfaceTypeDescriptor);
+  }
+
+  DexClassAndMethod ensureDefaultAsMethodOfCompanionClassStub(DexClassAndMethod method) {
+    if (method.isProgramMethod()) {
+      return ensureDefaultAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
+    }
+    ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
+    DexMethod companionMethodReference =
+        defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
+    return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
+  }
+
+  DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(DexClassAndMethod method) {
+    if (method.isProgramMethod()) {
+      return ensureStaticAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
+    } else {
+      ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
+      DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
+      return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
+    }
+  }
+
+  ProgramMethod ensureDefaultAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
+    DexEncodedMethod virtual = method.getDefinition();
+    DexMethod companionMethod =
+        defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
+    return InterfaceProcessor.ensureCompanionMethod(
+        method.getHolder(),
+        companionMethod.getName(),
+        companionMethod.getProto(),
+        appView,
+        methodBuilder -> {
+          MethodAccessFlags newFlags = method.getAccessFlags().copy();
+          newFlags.promoteToStatic();
+          methodBuilder
+              .setAccessFlags(newFlags)
+              .setGenericSignature(MethodTypeSignature.noSignature())
+              .setAnnotations(
+                  virtual
+                      .annotations()
+                      .methodParametersWithFakeThisArguments(appView.dexItemFactory()))
+              .setParameterAnnotationsList(
+                  virtual.getParameterAnnotations().withFakeThisParameter())
+              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
+              //  code to ensure it is never used before desugared and installed.
+              .setCode(
+                  syntheticMethod ->
+                      appView.enableWholeProgramOptimizations()
+                          ? virtual
+                              .getCode()
+                              .getCodeAsInlining(syntheticMethod, method.getReference())
+                          : InvalidCode.getInstance());
+        });
+  }
+
+  ProgramMethod ensurePrivateAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
+    DexMethod companionMethod =
+        privateAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
+    DexEncodedMethod definition = method.getDefinition();
+    return InterfaceProcessor.ensureCompanionMethod(
+        method.getHolder(),
+        companionMethod.getName(),
+        companionMethod.getProto(),
+        appView,
+        methodBuilder -> {
+          MethodAccessFlags newFlags = definition.getAccessFlags().copy();
+          assert newFlags.isPrivate();
+          newFlags.promoteToPublic();
+          newFlags.promoteToStatic();
+          methodBuilder
+              .setAccessFlags(newFlags)
+              .setGenericSignature(definition.getGenericSignature())
+              .setAnnotations(definition.annotations())
+              // TODO(b/183998768): Should this not also be updating with a fake 'this'
+              .setParameterAnnotationsList(definition.getParameterAnnotations())
+              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
+              //  code to ensure it is never used before desugared and installed.
+              .setCode(
+                  syntheticMethod ->
+                      appView.enableWholeProgramOptimizations()
+                          ? definition
+                              .getCode()
+                              .getCodeAsInlining(syntheticMethod, method.getReference())
+                          : InvalidCode.getInstance());
+        });
+  }
+
+  // Represent a static interface method as a method of companion class.
+  final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
+    DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
+    return rewritten;
+  }
+
+  private static DexMethod instanceAsMethodOfCompanionClass(
+      DexMethod method, String prefix, DexItemFactory factory) {
+    // Add an implicit argument to represent the receiver.
+    DexType[] params = method.proto.parameters.values;
+    DexType[] newParams = new DexType[params.length + 1];
+    newParams[0] = method.holder;
+    System.arraycopy(params, 0, newParams, 1, params.length);
+
+    // Add prefix to avoid name conflicts.
+    return factory.createMethod(
+        getCompanionClassType(method.holder, factory),
+        factory.createProto(method.proto.returnType, newParams),
+        factory.createString(prefix + method.name.toString()));
+  }
+
+  // Represent a default interface method as a method of companion class.
+  public static DexMethod defaultAsMethodOfCompanionClass(
+      DexMethod method, DexItemFactory factory) {
+    return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX, factory);
+  }
+
+  // Represent a private instance interface method as a method of companion class.
+  static DexMethod privateAsMethodOfCompanionClass(DexMethod method, DexItemFactory factory) {
+    // Add an implicit argument to represent the receiver.
+    return instanceAsMethodOfCompanionClass(method, PRIVATE_METHOD_PREFIX, factory);
+  }
+
+  DexMethod privateAsMethodOfCompanionClass(DexClassAndMethod method) {
+    return privateAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
+  }
+
+  private static DexClassAndMethod ensureMethodOfClasspathCompanionClassStub(
+      DexMethod companionMethodReference, ClasspathOrLibraryClass context, AppView<?> appView) {
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClasspathClassMethod(
+            companionMethodReference.getName(),
+            companionMethodReference.getProto(),
+            SyntheticKind.COMPANION_CLASS,
+            context,
+            appView,
+            classBuilder -> {},
+            methodBuilder ->
+                methodBuilder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
+  }
+
+  ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
+    DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
+    DexEncodedMethod definition = method.getDefinition();
+    return InterfaceProcessor.ensureCompanionMethod(
+        method.getHolder(),
+        companionMethodReference.getName(),
+        companionMethodReference.getProto(),
+        appView,
+        methodBuilder -> {
+          MethodAccessFlags newFlags = definition.getAccessFlags().copy();
+          newFlags.promoteToPublic();
+          methodBuilder
+              .setAccessFlags(newFlags)
+              .setGenericSignature(definition.getGenericSignature())
+              .setAnnotations(definition.annotations())
+              .setParameterAnnotationsList(definition.getParameterAnnotations())
+              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
+              //  code to ensure it is never used before desugared and installed.
+              .setCode(
+                  syntheticMethod ->
+                      appView.enableWholeProgramOptimizations()
+                          ? definition
+                              .getCode()
+                              .getCodeAsInlining(syntheticMethod, method.getReference())
+                          : InvalidCode.getInstance());
+        });
+  }
+
+  private Predicate<DexType> getShouldIgnoreFromReportsPredicate(AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    InternalOptions options = appView.options();
+    DexString companionClassNameDescriptorSuffix =
+        dexItemFactory.createString(
+            InterfaceDesugaringSyntheticHelper.COMPANION_CLASS_NAME_SUFFIX + ";");
+
+    return type -> {
+      DexString descriptor = type.getDescriptor();
+      return appView.rewritePrefix.hasRewrittenType(type, appView)
+          || descriptor.endsWith(companionClassNameDescriptorSuffix)
+          || isRewrittenEmulatedInterface(type)
+          || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(type)
+          || appView.getDontWarnConfiguration().matches(type);
+    };
+  }
+
+  boolean shouldIgnoreFromReports(DexType missing) {
+    return shouldIgnoreFromReportsPredicate.test(missing);
+  }
+
+  void warnMissingInterface(DexClass classToDesugar, DexClass implementing, DexType missing) {
+    // We use contains() on non hashed collection, but we know it's a 8 cases collection.
+    // j$ interfaces won't be missing, they are in the desugared library.
+    if (shouldIgnoreFromReports(missing)) {
+      return;
+    }
+    appView.options().warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
+  }
+}
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 71844b0..fafc920 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
@@ -15,6 +15,7 @@
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -25,30 +26,28 @@
   private final Flavor flavour;
   private final List<InterfaceDesugaringProcessor> interfaceDesugaringProcessors;
 
-  InterfaceMethodProcessorFacade(
-      AppView<?> appView, Flavor flavour, InterfaceMethodRewriter rewriter) {
+  InterfaceMethodProcessorFacade(AppView<?> appView, Flavor flavour) {
     this.appView = appView;
     this.flavour = flavour;
-    interfaceDesugaringProcessors = instantiateInterfaceDesugaringProcessors(appView, rewriter);
+    interfaceDesugaringProcessors = instantiateInterfaceDesugaringProcessors(appView);
   }
 
   private List<InterfaceDesugaringProcessor> instantiateInterfaceDesugaringProcessors(
-      AppView<?> appView, InterfaceMethodRewriter rewriter) {
+      AppView<?> appView) {
 
     // During L8 compilation, emulated interfaces are processed to be renamed, to have
     // their interfaces fixed-up and to generate the emulated dispatch code.
-    EmulatedInterfaceProcessor emulatedInterfaceProcessor =
-        new EmulatedInterfaceProcessor(appView, rewriter);
+    EmulatedInterfaceProcessor emulatedInterfaceProcessor = new EmulatedInterfaceProcessor(appView);
 
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
-    ClassProcessor classProcessor = new ClassProcessor(appView, rewriter);
+    ClassProcessor classProcessor = new ClassProcessor(appView);
 
     // Process interfaces, create companion or dispatch class if needed, move static
     // methods to companion class, copy default interface methods to companion classes,
     // make original default methods abstract, remove bridge methods, create dispatch
     // classes if needed.
-    InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView, rewriter);
+    InterfaceProcessor interfaceProcessor = new InterfaceProcessor(appView);
 
     // The processors can be listed in any order.
     return ImmutableList.of(classProcessor, interfaceProcessor, emulatedInterfaceProcessor);
@@ -60,7 +59,7 @@
 
     CollectingInterfaceDesugaringEventConsumer eventConsumer =
         new CollectingInterfaceDesugaringEventConsumer();
-    processClassesConcurrently(eventConsumer, executorService);
+    processClassesConcurrently(appView.appInfo().classes(), eventConsumer, executorService);
     converter.processMethodsConcurrently(
         eventConsumer.getSortedSynthesizedMethods(), executorService);
   }
@@ -101,11 +100,12 @@
   }
 
   private void processClassesConcurrently(
-      InterfaceProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      Collection<DexProgramClass> programClasses,
+      InterfaceProcessingDesugaringEventConsumer eventConsumer,
+      ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(
-        Iterables.filter(
-            appView.appInfo().classes(), (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
+        Iterables.filter(programClasses, (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
         clazz -> {
           for (InterfaceDesugaringProcessor processor : interfaceDesugaringProcessors) {
             processor.process(clazz, eventConsumer);
@@ -119,10 +119,12 @@
 
   @Override
   public void postProcessingDesugaring(
-      CfPostProcessingDesugaringEventConsumer eventConsumer, ExecutorService executorService)
+      Collection<DexProgramClass> programClasses,
+      CfPostProcessingDesugaringEventConsumer 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.
-    processClassesConcurrently(eventConsumer, executorService);
+    processClassesConcurrently(programClasses, eventConsumer, executorService);
   }
 }
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 3e93691..38ad3e4 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
@@ -24,7 +24,6 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -36,11 +35,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -69,11 +66,9 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.synthesis.SyntheticNaming;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -90,7 +85,6 @@
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 
 //
 // Default and static interface method desugaring rewriter (note that lambda
@@ -120,16 +114,10 @@
 //
 public final class InterfaceMethodRewriter implements CfInstructionDesugaring {
 
-  // Use InterfaceDesugaringForTesting for public accesses in tests.
-  static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
-  static final String COMPANION_CLASS_NAME_SUFFIX = "$-CC";
-  static final String DEFAULT_METHOD_PREFIX = "$default$";
-  static final String PRIVATE_METHOD_PREFIX = "$private$";
-
   private final AppView<?> appView;
   private final InternalOptions options;
   final DexItemFactory factory;
-  private final Map<DexType, DexType> emulatedInterfaces;
+  private final InterfaceDesugaringSyntheticHelper helper;
   // The emulatedMethod set is there to avoid doing the emulated look-up too often.
   private final Set<DexString> emulatedMethods = Sets.newIdentityHashSet();
 
@@ -139,8 +127,6 @@
   // Caches default interface method info for already processed interfaces.
   private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
 
-  private final Predicate<DexType> shouldIgnoreFromReportsPredicate;
-
   // This is used to filter out double desugaring on backported methods.
   private final BackportedMethodRewriter backportedMethodRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
@@ -163,8 +149,7 @@
     this.desugaredLibraryRetargeter = desugaredLibraryRetargeter;
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
-    this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
-    this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
+    this.helper = new InterfaceDesugaringSyntheticHelper(appView);
     initializeEmulatedInterfaceVariables();
   }
 
@@ -176,8 +161,7 @@
     this.desugaredLibraryRetargeter = null;
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
-    this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
-    this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
+    this.helper = new InterfaceDesugaringSyntheticHelper(appView);
     initializeEmulatedInterfaceVariables();
   }
 
@@ -214,6 +198,10 @@
     }
   }
 
+  public Set<DexString> getEmulatedMethods() {
+    return emulatedMethods;
+  }
+
   private void initializeEmulatedInterfaceVariables() {
     Map<DexType, DexType> emulateLibraryInterface =
         options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -234,28 +222,29 @@
       DexType emulatedInterface, String rewrittenEmulatedInterface) {
     addCompanionClassRewriteRule(emulatedInterface, rewrittenEmulatedInterface);
     appView.rewritePrefix.rewriteType(
-        getEmulateLibraryInterfaceClassType(emulatedInterface, factory),
+        InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType(
+            emulatedInterface, factory),
         factory.createType(
             DescriptorUtils.javaTypeToDescriptor(
-                rewrittenEmulatedInterface + EMULATE_LIBRARY_CLASS_NAME_SUFFIX)));
+                rewrittenEmulatedInterface
+                    + InterfaceDesugaringSyntheticHelper.EMULATE_LIBRARY_CLASS_NAME_SUFFIX)));
   }
 
-  void addCompanionClassRewriteRule(DexType interfaceType, String rewrittenType) {
+  private void addCompanionClassRewriteRule(DexType interfaceType, String rewrittenType) {
     addCompanionClassRewriteRule(interfaceType, rewrittenType, appView);
   }
 
   static void addCompanionClassRewriteRule(
       DexType interfaceType, String rewrittenType, AppView<?> appView) {
     appView.rewritePrefix.rewriteType(
-        getCompanionClassType(interfaceType, appView.dexItemFactory()),
+        InterfaceDesugaringSyntheticHelper.getCompanionClassType(
+            interfaceType, appView.dexItemFactory()),
         appView
             .dexItemFactory()
             .createType(
-                DescriptorUtils.javaTypeToDescriptor(rewrittenType + COMPANION_CLASS_NAME_SUFFIX)));
-  }
-
-  boolean isEmulatedInterface(DexType itf) {
-    return emulatedInterfaces.containsKey(itf);
+                DescriptorUtils.javaTypeToDescriptor(
+                    rewrittenType
+                        + InterfaceDesugaringSyntheticHelper.COMPANION_CLASS_NAME_SUFFIX)));
   }
 
   public boolean needsRewriting(DexMethod method, Type invokeType, ProgramMethod context) {
@@ -562,10 +551,6 @@
     return replacement;
   }
 
-  DexType getEmulatedInterface(DexType itf) {
-    return emulatedInterfaces.get(itf);
-  }
-
   private void leavingStaticInvokeToInterface(ProgramMethod method) {
     // When leaving static interface method invokes possibly upgrade the class file
     // version, but don't go above the initial class file version. If the input was
@@ -740,12 +725,15 @@
       if (directTarget.getDefinition().isPrivateMethod()) {
         companionMethod =
             directTarget.isProgramMethod()
-                ? ensurePrivateAsMethodOfProgramCompanionClassStub(directTarget.asProgramMethod())
+                ? helper
+                    .ensurePrivateAsMethodOfProgramCompanionClassStub(
+                        directTarget.asProgramMethod())
                     .getReference()
                 // TODO(b/183998768): Why does this not create a stub on the class path?
-                : privateAsMethodOfCompanionClass(directTarget);
+                : helper.privateAsMethodOfCompanionClass(directTarget);
       } else {
-        companionMethod = ensureDefaultAsMethodOfCompanionClassStub(directTarget).getReference();
+        companionMethod =
+            helper.ensureDefaultAsMethodOfCompanionClassStub(directTarget).getReference();
       }
       return rewriteInvoke.apply(companionMethod);
     } else {
@@ -756,7 +744,7 @@
         // This is a invoke-direct call to a virtual method.
         assert invokeNeedsRewriting(invokedMethod, DIRECT);
         return rewriteInvoke.apply(
-            ensureDefaultAsMethodOfCompanionClassStub(virtualTarget).getReference());
+            helper.ensureDefaultAsMethodOfCompanionClassStub(virtualTarget).getReference());
       } else {
         // The below assert is here because a well-type program should have a target, but we
         // cannot throw a compilation error, since we have no knowledge about the input.
@@ -868,7 +856,7 @@
     assert resolutionResult.getResolvedMethod().isStatic();
     assert invokeNeedsRewriting(invokedMethod, STATIC);
     DexClassAndMethod companionMethod =
-        ensureStaticAsMethodOfCompanionClassStub(resolutionResult.getResolutionPair());
+        helper.ensureStaticAsMethodOfCompanionClassStub(resolutionResult.getResolutionPair());
     return rewriteInvoke.apply(companionMethod.getReference());
   }
 
@@ -910,11 +898,12 @@
           return rewriteToThrow.apply(null);
         }
         return rewriteInvoke.apply(
-            privateAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
+            helper.privateAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
       } else {
         DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
         return rewriteInvoke.apply(
-            defaultAsMethodOfCompanionClass(amendedMethod, appView.dexItemFactory()));
+            InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
+                amendedMethod, appView.dexItemFactory()));
       }
     }
 
@@ -928,7 +917,7 @@
           if (holder.isLibraryClass() && holder.isInterface()) {
             assert invokeNeedsRewriting(invokedMethod, SUPER);
             return rewriteInvoke.apply(
-                ensureDefaultAsMethodOfCompanionClassStub(target).getReference());
+                helper.ensureDefaultAsMethodOfCompanionClassStub(target).getReference());
           }
         }
       }
@@ -956,7 +945,8 @@
         assert false;
         return null;
       }
-      DexClassAndMethod companionMethod = ensureDefaultAsMethodOfCompanionClassStub(emulatedMethod);
+      DexClassAndMethod companionMethod =
+          helper.ensureDefaultAsMethodOfCompanionClassStub(emulatedMethod);
       return rewriteInvoke.apply(companionMethod.getReference());
     }
     return null;
@@ -980,7 +970,7 @@
             || appView.options().isDesugaredLibraryCompilation())) {
       DexClassAndMethod defaultMethod =
           appView.definitionFor(emulatedItf).lookupClassMethod(invokedMethod);
-      if (defaultMethod != null && !dontRewrite(defaultMethod)) {
+      if (defaultMethod != null && !helper.dontRewrite(defaultMethod)) {
         assert !defaultMethod.getAccessFlags().isAbstract();
         return defaultMethod;
       }
@@ -995,7 +985,8 @@
     DexClassAndMethod defaultMethod =
         defaultMethodForEmulatedDispatchOrNull(invokedMethod, interfaceBit);
     if (defaultMethod != null) {
-      return rewriteInvoke.apply(emulateInterfaceLibraryMethod(defaultMethod, factory));
+      return rewriteInvoke.apply(
+          InterfaceDesugaringSyntheticHelper.emulateInterfaceLibraryMethod(defaultMethod, factory));
     }
     return null;
   }
@@ -1089,54 +1080,14 @@
       // interfaces.
       return null;
     }
-    if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.getHolderType())) {
+    if (!singleTarget.isAbstract() && helper.isEmulatedInterface(singleTarget.getHolderType())) {
       return singleTarget.getHolderType();
     }
     return null;
   }
 
   private boolean isNonDesugaredLibraryClass(DexClass clazz) {
-    return clazz.isLibraryClass() && !isInDesugaredLibrary(clazz);
-  }
-
-  boolean isInDesugaredLibrary(DexClass clazz) {
-    assert clazz.isLibraryClass() || options.isDesugaredLibraryCompilation();
-    if (emulatedInterfaces.containsKey(clazz.type)) {
-      return true;
-    }
-    return appView.rewritePrefix.hasRewrittenType(clazz.type, appView);
-  }
-
-  boolean dontRewrite(DexClassAndMethod method) {
-    for (Pair<DexType, DexString> dontRewrite :
-        options.desugaredLibraryConfiguration.getDontRewriteInvocation()) {
-      if (method.getHolderType() == dontRewrite.getFirst()
-          && method.getName() == dontRewrite.getSecond()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public static DexMethod emulateInterfaceLibraryMethod(
-      DexClassAndMethod method, DexItemFactory factory) {
-    return factory.createMethod(
-        getEmulateLibraryInterfaceClassType(method.getHolderType(), factory),
-        factory.prependTypeToProto(method.getHolderType(), method.getProto()),
-        method.getName());
-  }
-
-  private static String getEmulateLibraryInterfaceClassDescriptor(String descriptor) {
-    return descriptor.substring(0, descriptor.length() - 1)
-        + EMULATE_LIBRARY_CLASS_NAME_SUFFIX
-        + ";";
-  }
-
-  public static DexType getEmulateLibraryInterfaceClassType(DexType type, DexItemFactory factory) {
-    assert type.isClassType();
-    String descriptor = type.descriptor.toString();
-    String elTypeDescriptor = getEmulateLibraryInterfaceClassDescriptor(descriptor);
-    return factory.createSynthesizedType(elTypeDescriptor);
+    return clazz.isLibraryClass() && !helper.isInDesugaredLibrary(clazz);
   }
 
   private void reportStaticInterfaceMethodHandle(ProgramMethod context, DexMethodHandle handle) {
@@ -1155,147 +1106,6 @@
     }
   }
 
-  // Use InterfaceDesugaringForTesting for public accesses in tests.
-  static String getCompanionClassDescriptor(String descriptor) {
-    return descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";";
-  }
-
-  // Gets the companion class for the interface `type`.
-  static DexType getCompanionClassType(DexType type, DexItemFactory factory) {
-    assert type.isClassType();
-    String descriptor = type.descriptor.toString();
-    String ccTypeDescriptor = getCompanionClassDescriptor(descriptor);
-    return factory.createSynthesizedType(ccTypeDescriptor);
-  }
-
-  // Checks if `type` is a companion class.
-  public static boolean isCompanionClassType(DexType type) {
-    return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
-  }
-
-  public static boolean isEmulatedLibraryClassType(DexType type) {
-    return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
-  }
-
-  // Gets the interface class for a companion class `type`.
-  private DexType getInterfaceClassType(DexType type) {
-    return getInterfaceClassType(type, factory);
-  }
-
-  // Gets the interface class for a companion class `type`.
-  public static DexType getInterfaceClassType(DexType type, DexItemFactory factory) {
-    assert isCompanionClassType(type);
-    String descriptor = type.descriptor.toString();
-    String interfaceTypeDescriptor =
-        descriptor.substring(0, descriptor.length() - 1 - COMPANION_CLASS_NAME_SUFFIX.length())
-            + ";";
-    return factory.createType(interfaceTypeDescriptor);
-  }
-
-  DexClassAndMethod ensureDefaultAsMethodOfCompanionClassStub(DexClassAndMethod method) {
-    if (method.isProgramMethod()) {
-      return ensureDefaultAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
-    }
-    ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
-    DexMethod companionMethodReference =
-        defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
-    return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
-  }
-
-  DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(DexClassAndMethod method) {
-    if (method.isProgramMethod()) {
-      return ensureStaticAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
-    } else {
-      ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
-      DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
-      return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
-    }
-  }
-
-  ProgramMethod ensureDefaultAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
-    DexEncodedMethod virtual = method.getDefinition();
-    DexMethod companionMethod =
-        defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
-    return InterfaceProcessor.ensureCompanionMethod(
-        method.getHolder(),
-        companionMethod.getName(),
-        companionMethod.getProto(),
-        appView,
-        methodBuilder -> {
-          MethodAccessFlags newFlags = method.getAccessFlags().copy();
-          newFlags.promoteToStatic();
-          methodBuilder
-              .setAccessFlags(newFlags)
-              .setGenericSignature(MethodTypeSignature.noSignature())
-              .setAnnotations(
-                  virtual
-                      .annotations()
-                      .methodParametersWithFakeThisArguments(appView.dexItemFactory()))
-              .setParameterAnnotationsList(
-                  virtual.getParameterAnnotations().withFakeThisParameter())
-              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
-              //  code to ensure it is never used before desugared and installed.
-              .setCode(
-                  ignored ->
-                      appView.enableWholeProgramOptimizations()
-                          ? virtual.getCode()
-                          : InvalidCode.getInstance());
-        });
-  }
-
-  ProgramMethod ensurePrivateAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
-    DexMethod companionMethod =
-        privateAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory());
-    DexEncodedMethod definition = method.getDefinition();
-    return InterfaceProcessor.ensureCompanionMethod(
-        method.getHolder(),
-        companionMethod.getName(),
-        companionMethod.getProto(),
-        appView,
-        methodBuilder -> {
-          MethodAccessFlags newFlags = definition.getAccessFlags().copy();
-          assert newFlags.isPrivate();
-          newFlags.promoteToPublic();
-          newFlags.promoteToStatic();
-          methodBuilder
-              .setAccessFlags(newFlags)
-              .setGenericSignature(definition.getGenericSignature())
-              .setAnnotations(definition.annotations())
-              // TODO(b/183998768): Should this not also be updating with a fake 'this'
-              .setParameterAnnotationsList(definition.getParameterAnnotations())
-              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
-              //  code to ensure it is never used before desugared and installed.
-              .setCode(
-                  ignored ->
-                      appView.enableWholeProgramOptimizations()
-                          ? definition.getCode()
-                          : InvalidCode.getInstance());
-        });
-  }
-
-  // Represent a static interface method as a method of companion class.
-  final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
-    DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
-    return rewritten;
-  }
-
-  private static DexMethod instanceAsMethodOfCompanionClass(
-      DexMethod method, String prefix, DexItemFactory factory) {
-    // Add an implicit argument to represent the receiver.
-    DexType[] params = method.proto.parameters.values;
-    DexType[] newParams = new DexType[params.length + 1];
-    newParams[0] = method.holder;
-    System.arraycopy(params, 0, newParams, 1, params.length);
-
-    // Add prefix to avoid name conflicts.
-    return factory.createMethod(
-        getCompanionClassType(method.holder, factory),
-        factory.createProto(method.proto.returnType, newParams),
-        factory.createString(prefix + method.name.toString()));
-  }
-
   // It is possible that referenced method actually points to an interface which does
   // not define this default methods, but inherits it. We are making our best effort
   // to find an appropriate method, but still use the original one in case we fail.
@@ -1306,65 +1116,6 @@
     return singleCandidate != null ? singleCandidate : method;
   }
 
-  // Represent a default interface method as a method of companion class.
-  public static DexMethod defaultAsMethodOfCompanionClass(
-      DexMethod method, DexItemFactory factory) {
-    return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX, factory);
-  }
-
-  // Represent a private instance interface method as a method of companion class.
-  static DexMethod privateAsMethodOfCompanionClass(DexMethod method, DexItemFactory factory) {
-    // Add an implicit argument to represent the receiver.
-    return instanceAsMethodOfCompanionClass(method, PRIVATE_METHOD_PREFIX, factory);
-  }
-
-  private DexMethod privateAsMethodOfCompanionClass(DexClassAndMethod method) {
-    return privateAsMethodOfCompanionClass(method.getReference(), factory);
-  }
-
-  private static DexClassAndMethod ensureMethodOfClasspathCompanionClassStub(
-      DexMethod companionMethodReference, ClasspathOrLibraryClass context, AppView<?> appView) {
-    return appView
-        .getSyntheticItems()
-        .ensureFixedClasspathClassMethod(
-            companionMethodReference.getName(),
-            companionMethodReference.getProto(),
-            SyntheticKind.COMPANION_CLASS,
-            context,
-            appView,
-            classBuilder -> {},
-            methodBuilder ->
-                methodBuilder
-                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                    .setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
-  }
-
-  ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
-    DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
-    DexEncodedMethod definition = method.getDefinition();
-    return InterfaceProcessor.ensureCompanionMethod(
-        method.getHolder(),
-        companionMethodReference.getName(),
-        companionMethodReference.getProto(),
-        appView,
-        methodBuilder -> {
-          MethodAccessFlags newFlags = definition.getAccessFlags().copy();
-          newFlags.promoteToPublic();
-          methodBuilder
-              .setAccessFlags(newFlags)
-              .setGenericSignature(definition.getGenericSignature())
-              .setAnnotations(definition.annotations())
-              .setParameterAnnotationsList(definition.getParameterAnnotations())
-              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
-              //  code to ensure it is never used before desugared and installed.
-              .setCode(
-                  ignored ->
-                      appView.enableWholeProgramOptimizations()
-                          ? definition.getCode()
-                          : InvalidCode.getInstance());
-        });
-  }
-
   public void finalizeInterfaceMethodRewritingThroughIR(
       IRConverter converter, ExecutorService executorService) throws ExecutionException {
     SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.create();
@@ -1384,74 +1135,13 @@
   }
 
   public InterfaceMethodProcessorFacade getPostProcessingDesugaring(Flavor flavour) {
-    return new InterfaceMethodProcessorFacade(appView, flavour, this);
-  }
-
-  final boolean isDefaultMethod(DexEncodedMethod method) {
-    assert !method.accessFlags.isConstructor();
-    assert !method.accessFlags.isStatic();
-
-    if (method.accessFlags.isAbstract()) {
-      return false;
-    }
-    if (method.accessFlags.isNative()) {
-      throw new Unimplemented("Native default interface methods are not yet supported.");
-    }
-    if (!method.accessFlags.isPublic()) {
-      // NOTE: even though the class is allowed to have non-public interface methods
-      // with code, for example private methods, all such methods we are aware of are
-      // created by the compiler for stateful lambdas and they must be converted into
-      // static methods by lambda desugaring by this time.
-      throw new Unimplemented("Non public default interface methods are not yet supported.");
-    }
-    return true;
-  }
-
-  private Predicate<DexType> getShouldIgnoreFromReportsPredicate(AppView<?> appView) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    InternalOptions options = appView.options();
-    DexString companionClassNameDescriptorSuffix =
-        dexItemFactory.createString(COMPANION_CLASS_NAME_SUFFIX + ";");
-
-    return type -> {
-      DexString descriptor = type.getDescriptor();
-      return appView.rewritePrefix.hasRewrittenType(type, appView)
-          || descriptor.endsWith(companionClassNameDescriptorSuffix)
-          || emulatedInterfaces.containsValue(type)
-          || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(type)
-          || appView.getDontWarnConfiguration().matches(type);
-    };
-  }
-
-  private boolean shouldIgnoreFromReports(DexType missing) {
-    return shouldIgnoreFromReportsPredicate.test(missing);
-  }
-
-  void warnMissingInterface(DexClass classToDesugar, DexClass implementing, DexType missing) {
-    // We use contains() on non hashed collection, but we know it's a 8 cases collection.
-    // j$ interfaces won't be missing, they are in the desugared library.
-    if (shouldIgnoreFromReports(missing)) {
-      return;
-    }
-    options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
-  }
-
-  private void warnMissingType(ProgramMethod context, DexType missing) {
-    // Companion/Emulated interface/Conversion classes for desugared library won't be missing,
-    // they are in the desugared library.
-    if (shouldIgnoreFromReports(missing)) {
-      return;
-    }
-    DexMethod method = appView.graphLens().getOriginalMethodSignature(context.getReference());
-    Origin origin = getMethodOrigin(method);
-    MethodPosition position = new MethodPosition(method.asMethodReference());
-    options.warningMissingTypeForDesugar(origin, position, missing, method);
+    return new InterfaceMethodProcessorFacade(appView, flavour);
   }
 
   private Origin getMethodOrigin(DexMethod method) {
     DexType holder = method.holder;
-    if (isCompanionClassType(holder)) {
-      holder = getInterfaceClassType(holder);
+    if (InterfaceDesugaringSyntheticHelper.isCompanionClassType(holder)) {
+      holder = helper.getInterfaceClassType(holder);
     }
     DexClass clazz = appView.definitionFor(holder);
     return clazz == null ? Origin.unknown() : clazz.getOrigin();
@@ -1473,7 +1163,7 @@
     DefaultMethodsHelper helper = new DefaultMethodsHelper();
     DexClass definedInterface = appView.definitionFor(iface);
     if (definedInterface == null) {
-      warnMissingInterface(classToDesugar, implementing, iface);
+      this.helper.warnMissingInterface(classToDesugar, implementing, iface);
       return helper.wrapInCollection();
     }
     if (!definedInterface.isInterface()) {
@@ -1511,7 +1201,7 @@
 
     // Add all default methods of this interface.
     for (DexEncodedMethod encoded : definedInterface.virtualMethods()) {
-      if (isDefaultMethod(encoded)) {
+      if (this.helper.isCompatibleDefaultMethod(encoded)) {
         helper.addDefaultMethod(encoded);
       }
     }
@@ -1519,6 +1209,18 @@
     return helper.wrapInCollection();
   }
 
+  private void warnMissingType(ProgramMethod context, DexType missing) {
+    // Companion/Emulated interface/Conversion classes for desugared library won't be missing,
+    // they are in the desugared library.
+    if (helper.shouldIgnoreFromReports(missing)) {
+      return;
+    }
+    DexMethod method = appView.graphLens().getOriginalMethodSignature(context.getReference());
+    Origin origin = getMethodOrigin(method);
+    MethodPosition position = new MethodPosition(method.asMethodReference());
+    options.warningMissingTypeForDesugar(origin, position, missing, method);
+  }
+
   public static void reportDependencyEdge(
       DexClass dependent, DexClass dependency, AppInfo appInfo) {
     assert !dependent.isLibraryClass();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriterFixup.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriterFixup.java
index b3d5f1b..1e4b4dc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriterFixup.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriterFixup.java
@@ -20,9 +20,6 @@
   }
 
   void run() {
-    if (graphLens == null) {
-      return;
-    }
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (clazz.getEnclosingMethodAttribute() != null
           && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 2dd65a9..c917520 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
+import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayDeque;
@@ -69,13 +70,13 @@
 public final class InterfaceProcessor implements InterfaceDesugaringProcessor {
 
   private final AppView<?> appView;
-  private final InterfaceMethodRewriter rewriter;
+  private final InterfaceDesugaringSyntheticHelper helper;
   private final Map<DexProgramClass, PostProcessingInterfaceInfo> postProcessingInterfaceInfos =
       new ConcurrentHashMap<>();
 
-  InterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
+  InterfaceProcessor(AppView<?> appView) {
     this.appView = appView;
-    this.rewriter = rewriter;
+    helper = new InterfaceDesugaringSyntheticHelper(appView);
   }
 
   @Override
@@ -222,7 +223,7 @@
   private void processVirtualInterfaceMethods(DexProgramClass iface) {
     for (ProgramMethod method : iface.virtualProgramMethods()) {
       DexEncodedMethod virtual = method.getDefinition();
-      if (rewriter.isDefaultMethod(virtual)) {
+      if (helper.isCompatibleDefaultMethod(virtual)) {
         if (!canMoveToCompanionClass(virtual)) {
           throw new CompilationError(
               "One or more instruction is preventing default interface "
@@ -237,7 +238,7 @@
               iface.origin);
         }
         // Create a new method in a companion class to represent default method implementation.
-        ProgramMethod companion = rewriter.ensureDefaultAsMethodOfProgramCompanionClassStub(method);
+        ProgramMethod companion = helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method);
         DexEncodedMethod.setDebugInfoWithFakeThisParameter(
             code, companion.getReference().getArity(), appView);
         finalizeMoveToCompanionMethod(method, companion);
@@ -270,7 +271,7 @@
                 + " is expected to "
                 + "either be public or private in "
                 + iface.origin;
-        companion = rewriter.ensureStaticAsMethodOfProgramCompanionClassStub(method);
+        companion = helper.ensureStaticAsMethodOfProgramCompanionClassStub(method);
       } else {
         assert definition.isPrivate();
         Code code = definition.getCode();
@@ -281,7 +282,7 @@
                   + method.getReference().toSourceString(),
               iface.origin);
         }
-        companion = rewriter.ensurePrivateAsMethodOfProgramCompanionClassStub(method);
+        companion = helper.ensurePrivateAsMethodOfProgramCompanionClassStub(method);
         DexEncodedMethod.setDebugInfoWithFakeThisParameter(
             code, companion.getReference().getArity(), appView);
       }
@@ -302,7 +303,11 @@
     if (definition.hasClassFileVersion()) {
       companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion());
     }
-    companion.getDefinition().setCode(definition.getCode(), appView);
+    companion
+        .getDefinition()
+        .setCode(
+            definition.getCode().getCodeAsInlining(companion.getReference(), method.getReference()),
+            appView);
     definition.setCode(InvalidCode.getInstance(), appView);
   }
 
@@ -389,7 +394,7 @@
       throw new Unimplemented("Native interface methods are not yet supported.");
     }
     return method.accessFlags.isStatic()
-        && !rewriter.factory.isClassConstructor(method.getReference());
+        && !appView.dexItemFactory().isClassConstructor(method.getReference());
   }
 
   private InterfaceProcessorNestedGraphLens postProcessInterfaces() {
@@ -443,10 +448,14 @@
   @Override
   public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
     InterfaceProcessorNestedGraphLens graphLens = postProcessInterfaces();
-    if (appView.enableWholeProgramOptimizations() && graphLens != null) {
-      appView.setGraphLens(graphLens);
+    if (graphLens != null) {
+      if (appView.enableWholeProgramOptimizations()) {
+        appView.setGraphLens(graphLens);
+      }
+      new InterfaceMethodRewriterFixup(appView, graphLens).run();
+
+      graphLens.moveToPending();
     }
-    new InterfaceMethodRewriterFixup(appView, graphLens).run();
   }
 
   private PostProcessingInterfaceInfo getPostProcessingInterfaceInfo(DexProgramClass iface) {
@@ -510,9 +519,14 @@
 
   // Specific lens which remaps invocation types to static since all rewrites performed here
   // are to static companion methods.
+  // TODO(b/167345026): Remove the use of this lens.
   public static class InterfaceProcessorNestedGraphLens extends NestedGraphLens {
 
     private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> extraNewMethodSignatures;
+    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
+        pendingNewMethodSignatures;
+    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
+        pendingExtraNewMethodSignatures;
 
     public InterfaceProcessorNestedGraphLens(
         AppView<?> appView,
@@ -524,6 +538,15 @@
       this.extraNewMethodSignatures = extraNewMethodSignatures;
     }
 
+    public void moveToPending() {
+      // These are "pending" and installed in the "toggled" state only.
+      pendingNewMethodSignatures = newMethodSignatures;
+      pendingExtraNewMethodSignatures = extraNewMethodSignatures;
+      // The interface methods do not contribute to renaming lens info anymore, so they are cleared.
+      newMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
+      this.extraNewMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
+    }
+
     public static InterfaceProcessorNestedGraphLens find(GraphLens lens) {
       if (lens.isInterfaceProcessorLens()) {
         return lens.asInterfaceProcessorLens();
@@ -538,10 +561,14 @@
       return null;
     }
 
-    public void toggleMappingToExtraMethods() {
-      BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> tmp = newMethodSignatures;
-      this.newMethodSignatures = extraNewMethodSignatures;
-      this.extraNewMethodSignatures = tmp;
+    public void enableMapping() {
+      this.newMethodSignatures = pendingExtraNewMethodSignatures;
+      this.extraNewMethodSignatures = pendingNewMethodSignatures;
+    }
+
+    public void disableMapping() {
+      this.newMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
+      this.extraNewMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
     }
 
     public BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
@@ -586,8 +613,6 @@
           new BidirectionalOneToOneHashMap<>();
 
       public void recordCodeMovedToCompanionClass(DexMethod from, DexMethod to) {
-        assert from != to;
-        methodMap.put(from, from);
         extraNewMethodSignatures.put(from, to);
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index ddb7ff5..efc31d3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 5a30d1d..e58024e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -12,21 +12,13 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleValue;
-import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.ConstNumber;
 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.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.PostOptimization;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
@@ -41,8 +33,6 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -55,27 +45,39 @@
   // For now, before revisiting methods with more precise argument info, we switch the mode.
   // Then, revisiting a target at a certain level will not improve call site information of
   // callees in lower levels.
-  private enum Mode {
-    COLLECT, // Set until the end of the 1st round of IR processing. CallSiteOptimizationInfo will
-             // be updated in this mode only.
-    REVISIT  // Set once the all methods are processed. IRBuilder will add other instructions that
-             // reflect collected CallSiteOptimizationInfo.
+  public enum Mode {
+    // Set until the end of the 1st round of IR processing. CallSiteOptimizationInfo will be updated
+    // in this mode only.
+    COLLECT,
+    // Set once the all methods are processed. IRBuilder will add other instructions that reflect
+    // collected CallSiteOptimizationInfo.
+    REVISIT;
+
+    public boolean isRevisit() {
+      return this == REVISIT;
+    }
   }
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final CallSiteOptimizationOptions options;
+  private final InternalOptions options;
+  private final CallSiteOptimizationOptions optimizationOptions;
   private ProgramMethodSet revisitedMethods = null;
   private Mode mode = Mode.COLLECT;
 
   public CallSiteOptimizationInfoPropagator(AppView<AppInfoWithLiveness> appView) {
     assert appView.enableWholeProgramOptimizations();
     this.appView = appView;
-    this.options = appView.options().callSiteOptimizationOptions();
+    this.options = appView.options();
+    this.optimizationOptions = appView.options().callSiteOptimizationOptions();
     if (Log.isLoggingEnabledFor(CallSiteOptimizationInfoPropagator.class)) {
       revisitedMethods = ProgramMethodSet.create();
     }
   }
 
+  public Mode getMode() {
+    return mode;
+  }
+
   public void logResults() {
     assert Log.ENABLED;
     if (revisitedMethods != null) {
@@ -189,7 +191,7 @@
       return;
     }
 
-    if (targets.size() > options.getMaxNumberOfDispatchTargetsBeforeAbandoning()) {
+    if (targets.size() > optimizationOptions.getMaxNumberOfDispatchTargetsBeforeAbandoning()) {
       // If the number of targets exceed the threshold, abandon call site optimization for all
       // targets.
       abandonCallSitePropagation(invoke, resolutionResult, targets, context);
@@ -327,7 +329,9 @@
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
         if (virtualProgramMethod.getDefinition().isNonPrivateVirtualMethod()
-            && appView.getKeepInfo().isPinned(virtualProgramMethod.getReference(), appView)) {
+            && appView
+                .getKeepInfo()
+                .isPinned(virtualProgramMethod.getReference(), appView, options)) {
           consumer.accept(virtualProgramMethod);
         }
       }
@@ -375,110 +379,6 @@
     return callSiteOptimizationInfo;
   }
 
-  // If collected call site optimization info has something useful, e.g., non-null argument,
-  // insert corresponding assume instructions for arguments.
-  public void applyCallSiteOptimizationInfo(
-      IRCode code, CallSiteOptimizationInfo callSiteOptimizationInfo) {
-    if (mode != Mode.REVISIT) {
-      return;
-    }
-    // TODO(b/139246447): Assert no BOTTOM left.
-    if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, code.method())) {
-      return;
-    }
-    Set<Value> affectedValues = Sets.newIdentityHashSet();
-    List<Assume> assumeInstructions = new LinkedList<>();
-    List<Instruction> constants = new LinkedList<>();
-    int argumentsSeen = 0;
-    InstructionListIterator iterator = code.entryBlock().listIterator(code);
-    while (iterator.hasNext()) {
-      Instruction instr = iterator.next();
-      if (!instr.isArgument()) {
-        break;
-      }
-      argumentsSeen++;
-      Value originalArg = instr.asArgument().outValue();
-      if (originalArg.hasLocalInfo() || !originalArg.getType().isReferenceType()) {
-        continue;
-      }
-      int argIndex = argumentsSeen - 1;
-      AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(argIndex);
-      if (abstractValue.isSingleValue()) {
-        assert options.isConstantPropagationEnabled();
-        SingleValue singleValue = abstractValue.asSingleValue();
-        if (singleValue.isMaterializableInContext(appView, code.context())) {
-          Instruction replacement =
-              singleValue.createMaterializingInstruction(appView, code, instr);
-          replacement.setPosition(instr.getPosition());
-          affectedValues.addAll(originalArg.affectedValues());
-          originalArg.replaceUsers(replacement.outValue());
-          constants.add(replacement);
-          continue;
-        }
-      }
-      TypeElement dynamicUpperBoundType =
-          callSiteOptimizationInfo.getDynamicUpperBoundType(argIndex);
-      if (dynamicUpperBoundType == null) {
-        continue;
-      }
-      if (dynamicUpperBoundType.isDefinitelyNull()) {
-        ConstNumber nullInstruction = code.createConstNull();
-        nullInstruction.setPosition(instr.getPosition());
-        affectedValues.addAll(originalArg.affectedValues());
-        originalArg.replaceUsers(nullInstruction.outValue());
-        constants.add(nullInstruction);
-        continue;
-      }
-      Value specializedArg;
-      if (dynamicUpperBoundType.strictlyLessThan(originalArg.getType(), appView)) {
-        specializedArg = code.createValue(originalArg.getType());
-        affectedValues.addAll(originalArg.affectedValues());
-        originalArg.replaceUsers(specializedArg);
-        Assume assumeType =
-            Assume.createAssumeDynamicTypeInstruction(
-                dynamicUpperBoundType, null, specializedArg, originalArg, instr, appView);
-        assumeType.setPosition(instr.getPosition());
-        assumeInstructions.add(assumeType);
-      } else {
-        specializedArg = originalArg;
-      }
-      assert specializedArg != null && specializedArg.getType().isReferenceType();
-      if (dynamicUpperBoundType.isDefinitelyNotNull()) {
-        // If we already knew `arg` is never null, e.g., receiver, skip adding non-null.
-        if (!specializedArg.getType().isDefinitelyNotNull()) {
-          Value nonNullArg =
-              code.createValue(specializedArg.getType().asReferenceType().asMeetWithNotNull());
-          affectedValues.addAll(specializedArg.affectedValues());
-          specializedArg.replaceUsers(nonNullArg);
-          Assume assumeNotNull =
-              Assume.createAssumeNonNullInstruction(nonNullArg, specializedArg, instr, appView);
-          assumeNotNull.setPosition(instr.getPosition());
-          assumeInstructions.add(assumeNotNull);
-        }
-      }
-    }
-    assert argumentsSeen
-            == code.method().getReference().getArity() + (code.method().isStatic() ? 0 : 1)
-        : "args: "
-            + argumentsSeen
-            + " != "
-            + "arity: "
-            + code.method().getReference().getArity()
-            + ", static: "
-            + code.method().isStatic();
-    // After packed Argument instructions, add Assume and constant instructions.
-    assert !iterator.peekPrevious().isArgument();
-    iterator.previous();
-    assert iterator.peekPrevious().isArgument();
-    assumeInstructions.forEach(iterator::add);
-    // TODO(b/69963623): Can update method signature and save more on call sites.
-    constants.forEach(iterator::add);
-
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
-  }
-
   @Override
   public ProgramMethodSet methodsToRevisit() {
     mode = Mode.REVISIT;
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 8c5f439..0a42896 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
@@ -16,8 +16,8 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 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.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.code.BasicBlock;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 55780b6..ca6c387 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
+import static com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult.isOverriding;
 
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
@@ -11,8 +11,8 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 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.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
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 c25755c..7534cba 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
@@ -6,8 +6,8 @@
 
 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.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index b3dd6b6..28530d3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
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 e4d045d..fdc9208 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
@@ -19,9 +19,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 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.Nullability;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 17db09c..dc88779 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -17,8 +17,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -174,10 +174,11 @@
     if (lookup.holder.isArrayType()) {
       return ConstraintWithTarget.ALWAYS;
     }
-    ResolutionResult resolutionResult = appView.appInfo().unsafeResolveMethodDueToDexFormat(lookup);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().unsafeResolveMethodDueToDexFormat(lookup);
     DexEncodedMethod target =
         singleTargetWhileVerticalClassMerging(
-            resolutionResult, context, ResolutionResult::lookupInvokeDirectTarget);
+            resolutionResult, context, MethodResolutionResult::lookupInvokeDirectTarget);
     return forResolvedMember(resolutionResult.getInitialResolutionHolder(), context, target);
   }
 
@@ -205,10 +206,11 @@
     if (lookup.holder.isArrayType()) {
       return ConstraintWithTarget.ALWAYS;
     }
-    ResolutionResult resolutionResult = appView.appInfo().unsafeResolveMethodDueToDexFormat(lookup);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().unsafeResolveMethodDueToDexFormat(lookup);
     DexEncodedMethod target =
         singleTargetWhileVerticalClassMerging(
-            resolutionResult, context, ResolutionResult::lookupInvokeStaticTarget);
+            resolutionResult, context, MethodResolutionResult::lookupInvokeStaticTarget);
     if (!allowStaticInterfaceMethodCalls && target != null) {
       // See b/120121170.
       DexClass methodClass = appView.definitionFor(graphLens.lookupType(target.getHolderType()));
@@ -221,9 +223,10 @@
 
   @SuppressWarnings("ConstantConditions")
   private DexEncodedMethod singleTargetWhileVerticalClassMerging(
-      ResolutionResult resolutionResult,
+      MethodResolutionResult resolutionResult,
       ProgramMethod context,
-      TriFunction<ResolutionResult, DexProgramClass, AppInfoWithClassHierarchy, DexEncodedMethod>
+      TriFunction<
+              MethodResolutionResult, DexProgramClass, AppInfoWithClassHierarchy, DexEncodedMethod>
           lookup) {
     if (!resolutionResult.isSingleResolution()) {
       return null;
@@ -357,7 +360,7 @@
 
     // Perform resolution and derive inlining constraints based on the accessibility of the
     // resolution result.
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, isInterface);
+    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, isInterface);
     if (!resolutionResult.isVirtualTarget()) {
       return ConstraintWithTarget.NEVER;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 5518ab7..1e5c3ae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index ae48979..9608eed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -15,8 +15,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 191d6af..c07ad6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1322,7 +1322,7 @@
     InterfaceProcessorNestedGraphLens interfaceProcessorLens =
         InterfaceProcessorNestedGraphLens.find(appView.graphLens());
     if (interfaceProcessorLens != null) {
-      interfaceProcessorLens.toggleMappingToExtraMethods();
+      interfaceProcessorLens.enableMapping();
     }
 
     for (LongLivedProgramMethodMultisetBuilder outlineMethods : candidateMethodLists) {
@@ -1335,7 +1335,7 @@
     // TODO(b/167345026): Remove once default interface methods are desugared prior to the first
     //  optimization pass.
     if (interfaceProcessorLens != null) {
-      interfaceProcessorLens.toggleMappingToExtraMethods();
+      interfaceProcessorLens.disableMapping();
     }
 
     candidateMethodLists.clear();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 5536f0e..a40fca9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -88,7 +88,7 @@
 
   private void processClasses(DexProgramClass clazz) {
     // Switchmap classes are synthetic and have a class initializer.
-    if (!clazz.accessFlags.isSynthetic() && !clazz.hasClassInitializer()) {
+    if (!clazz.accessFlags.isSynthetic() || !clazz.hasClassInitializer()) {
       return;
     }
     List<DexEncodedField> switchMapFields = clazz.staticFields().stream()
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 01d23b6..65f0d3f 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
@@ -21,9 +21,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.LibraryMethod;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 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;
@@ -1021,7 +1021,7 @@
     // We should not inline a method if the invocation has type interface or virtual and the
     // signature of the invocation resolves to a private or static method.
     // TODO(b/147212189): Why not inline private methods? If access is permitted it is valid.
-    ResolutionResult resolutionResult =
+    MethodResolutionResult resolutionResult =
         appView.appInfo().resolveMethodOnClass(callee, eligibleClass);
     if (resolutionResult.isSingleResolution()
         && !resolutionResult.getSingleTarget().isNonPrivateVirtualMethod()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
index 8cbade6..822b965 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -23,8 +23,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index eb6014b..a5ed9fb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -37,9 +37,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
@@ -898,7 +898,8 @@
       }
       // Perform resolution and derive unboxing constraints based on the accessibility of the
       // resolution result.
-      ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method, isInterface);
+      MethodResolutionResult resolutionResult =
+          appView.appInfo().resolveMethod(method, isInterface);
       if (!resolutionResult.isVirtualTarget()) {
         constraint = Constraint.NEVER;
         return;
@@ -941,7 +942,7 @@
       if (method.holder.isArrayType()) {
         return;
       }
-      ResolutionResult resolutionResult =
+      MethodResolutionResult resolutionResult =
           appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
       DexEncodedMethod target = resolutionResult.getSingleTarget();
       if (target == null || !methodValidator.test(target)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 8530a9d3..a675735 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.InternalOptions;
 
 class EnumUnboxingCandidateAnalysis {
 
@@ -100,8 +101,9 @@
     // also kept. This is to allow the keep rule -keepclassmembers to be used on enums while
     // enum unboxing can still be performed.
     KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
-    keepInfo.forEachPinnedType(this::removePinnedCandidate);
-    keepInfo.forEachPinnedField(field -> removePinnedIfNotHolder(field, field.type));
+    InternalOptions options = appView.options();
+    keepInfo.forEachPinnedType(this::removePinnedCandidate, options);
+    keepInfo.forEachPinnedField(field -> removePinnedIfNotHolder(field, field.type), options);
     keepInfo.forEachPinnedMethod(
         method -> {
           DexProto proto = method.proto;
@@ -109,7 +111,8 @@
           for (DexType parameterType : proto.parameters.values) {
             removePinnedIfNotHolder(method, parameterType);
           }
-        });
+        },
+        options);
   }
 
   private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 83fdf6a..ed27028 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -9,12 +9,17 @@
 import com.android.tools.r8.graph.AppView;
 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.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -193,6 +198,68 @@
     return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
   }
 
+  public static CallSiteOptimizationInfo fromMethodState(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod method,
+      ConcreteMonomorphicMethodState methodState) {
+    boolean allowConstantPropagation =
+        appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
+    ConcreteCallSiteOptimizationInfo newCallSiteInfo =
+        new ConcreteCallSiteOptimizationInfo(methodState.size(), allowConstantPropagation);
+    boolean isTop = true;
+    for (int argumentIndex = 0; argumentIndex < methodState.size(); argumentIndex++) {
+      ParameterState parameterState = methodState.getParameterState(argumentIndex);
+      if (parameterState.isUnknown()) {
+        continue;
+      }
+
+      ConcreteParameterState concreteParameterState = parameterState.asConcrete();
+
+      // Constant propagation.
+      if (allowConstantPropagation) {
+        AbstractValue abstractValue = concreteParameterState.getAbstractValue();
+        if (abstractValue.isNonTrivial()) {
+          newCallSiteInfo.constants.put(argumentIndex, abstractValue);
+          isTop = false;
+        }
+      }
+
+      // Type propagation.
+      DexType staticType = method.getDefinition().getArgumentType(argumentIndex);
+      if (staticType.isReferenceType()) {
+        TypeElement staticTypeElement = staticType.toTypeElement(appView);
+        if (staticType.isArrayType()) {
+          Nullability nullability = concreteParameterState.asArrayParameter().getNullability();
+          if (nullability.isDefinitelyNull()) {
+            if (allowConstantPropagation) {
+              newCallSiteInfo.constants.put(
+                  argumentIndex, appView.abstractValueFactory().createNullValue());
+            }
+          } else if (nullability.isDefinitelyNotNull()) {
+            newCallSiteInfo.dynamicUpperBoundTypes.put(
+                argumentIndex, staticTypeElement.asArrayType().asDefinitelyNotNull());
+          } else {
+            // Should never happen, since the parameter state is unknown in this case.
+            assert false;
+          }
+        } else if (staticType.isClassType()) {
+          DynamicType dynamicType =
+              method.getDefinition().isInstance() && argumentIndex == 0
+                  ? concreteParameterState.asReceiverParameter().getDynamicType()
+                  : concreteParameterState.asClassParameter().getDynamicType();
+          if (!dynamicType.isTrivial(staticTypeElement)) {
+            newCallSiteInfo.dynamicUpperBoundTypes.put(
+                argumentIndex, dynamicType.getDynamicUpperBoundType());
+            isTop = false;
+          } else {
+            newCallSiteInfo.dynamicUpperBoundTypes.put(argumentIndex, staticTypeElement);
+          }
+        }
+      }
+    }
+    return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
+  }
+
   @Override
   public boolean isConcreteCallSiteOptimizationInfo() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index ccb3076..6abfb55 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -52,8 +52,8 @@
 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.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
 import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
@@ -987,7 +987,7 @@
       return false;
     }
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    ResolutionResult resolutionResult =
+    MethodResolutionResult resolutionResult =
         appView
             .appInfo()
             .resolveMethodOnClass(appView.dexItemFactory().objectMembers.finalize, clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 0f81ec0..54184d78 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -251,6 +251,10 @@
     this.classInlinerConstraint = classInlinerConstraint;
   }
 
+  void unsetClassInlinerMethodConstraint() {
+    this.classInlinerConstraint = ClassInlinerMethodConstraint.alwaysFalse();
+  }
+
   @Override
   public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
     return enumUnboxerMethodClassification;
@@ -338,6 +342,10 @@
     this.bridgeInfo = bridgeInfo;
   }
 
+  void unsetBridgeInfo() {
+    this.bridgeInfo = null;
+  }
+
   @Override
   public AbstractValue getAbstractReturnValue() {
     return abstractReturnValue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index b248a6d..fe00440 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -163,12 +163,27 @@
     method.getMutableOptimizationInfo().setBridgeInfo(bridgeInfo);
   }
 
+  public void unsetBridgeInfo(DexEncodedMethod method) {
+    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+      method.getOptimizationInfo().asMutableMethodOptimizationInfo().unsetBridgeInfo();
+    }
+  }
+
   @Override
   public void setClassInlinerMethodConstraint(
       ProgramMethod method, ClassInlinerMethodConstraint classInlinerConstraint) {
     // Ignored.
   }
 
+  public void unsetClassInlinerMethodConstraint(ProgramMethod method) {
+    if (method.getOptimizationInfo().isMutableOptimizationInfo()) {
+      method
+          .getOptimizationInfo()
+          .asMutableMethodOptimizationInfo()
+          .unsetClassInlinerMethodConstraint();
+    }
+  }
+
   @Override
   public void setEnumUnboxerMethodClassification(
       ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
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 08199da..4b7cffb 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
@@ -183,16 +183,8 @@
         }
         instructionIterator.removeOrReplaceByDebugLocalRead();
       } else if (singleTarget.getReference() == objectsMethods.requireNonNullElseGet) {
-        // Optimize Objects.requireNonNullElseGet(null, supplier) into supplier.get().
-        if (invoke.hasOutValue()) {
-          invoke.outValue().replaceUsers(invoke.getLastArgument(), affectedValues);
-        }
-        instructionIterator.replaceCurrentInstruction(
-            InvokeVirtual.builder()
-                .setMethod(dexItemFactory.supplierMembers.get)
-                .setOutValue(invoke.outValue())
-                .setSingleArgument(invoke.getLastArgument())
-                .build());
+        // Don't optimize Objects.requireNonNullElseGet. The result of calling supplier.get() still
+        // needs a null-check, so two invokes will be needed.
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
index 5f74465..34514e3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -9,7 +9,7 @@
 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.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
index 5ddba94..85fb76a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMember;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo.AssumeType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index f600a41..1f6c00a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -658,7 +658,7 @@
         return false;
       }
       AppInfoWithLiveness appInfo = appView.appInfo();
-      ResolutionResult resolutionResult =
+      MethodResolutionResult resolutionResult =
           appInfo.unsafeResolveMethodDueToDexFormat(methodReferenced);
       DexEncodedMethod methodInvoked =
           user.isInvokeDirect()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 09790f1..c3f2bc3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -141,6 +141,7 @@
       converter.markProcessed(code, feedback);
       // Fixup method optimization info (the method no longer returns a constant).
       feedback.unsetAbstractReturnValue(parentMethod.getDefinition());
+      feedback.unsetClassInlinerMethodConstraint(parentMethod);
     } else {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index db93ced..4ce1fb6 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -32,6 +32,9 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import java.util.ArrayList;
@@ -44,17 +47,6 @@
     super(appView, holder);
   }
 
-  boolean shouldConvert(DexType type, DesugaredLibraryAPIConverter converter, DexMethod method) {
-    if (!appView.rewritePrefix.hasRewrittenType(type, appView)) {
-      return false;
-    }
-    if (converter.canConvert(type)) {
-      return true;
-    }
-    converter.reportInvalidInvoke(type, method, "");
-    return false;
-  }
-
   DexType vivifiedTypeFor(DexType type) {
     return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
   }
@@ -62,22 +54,25 @@
   public static class APIConverterVivifiedWrapperCfCodeProvider
       extends DesugaredLibraryAPIConversionCfCodeProvider {
 
-    DexField wrapperField;
-    DexMethod forwardMethod;
-    DesugaredLibraryAPIConverter converter;
-    boolean itfCall;
+    private final DexField wrapperField;
+    private final DexMethod forwardMethod;
+    private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizer;
+    private final boolean itfCall;
+    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
     public APIConverterVivifiedWrapperCfCodeProvider(
         AppView<?> appView,
         DexMethod forwardMethod,
         DexField wrapperField,
-        DesugaredLibraryAPIConverter converter,
-        boolean itfCall) {
+        DesugaredLibraryWrapperSynthesizer wrapperSynthesizer,
+        boolean itfCall,
+        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
       super(appView, wrapperField.holder);
       this.forwardMethod = forwardMethod;
       this.wrapperField = wrapperField;
-      this.converter = converter;
+      this.wrapperSynthesizer = wrapperSynthesizer;
       this.itfCall = itfCall;
+      this.eventConsumer = eventConsumer;
     }
 
     @Override
@@ -94,11 +89,12 @@
       DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, forwardMethod)) {
+        if (wrapperSynthesizer.shouldConvert(param, forwardMethod)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.ensureConversionMethod(param, param, vivifiedTypeFor(param)),
+                  wrapperSynthesizer.ensureConversionMethod(
+                      param, param, vivifiedTypeFor(param), eventConsumer),
                   false));
           newParameters[index - 1] = vivifiedTypeFor(param);
         }
@@ -125,12 +121,12 @@
         instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newForwardMethod, false));
       }
 
-      if (shouldConvert(returnType, converter, forwardMethod)) {
+      if (wrapperSynthesizer.shouldConvert(returnType, forwardMethod)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
-                converter.ensureConversionMethod(
-                    returnType, vivifiedTypeFor(returnType), returnType),
+                wrapperSynthesizer.ensureConversionMethod(
+                    returnType, vivifiedTypeFor(returnType), returnType, eventConsumer),
                 false));
       }
       if (returnType == factory.voidType) {
@@ -147,21 +143,24 @@
 
     DexField wrapperField;
     DexMethod forwardMethod;
-    DesugaredLibraryAPIConverter converter;
+    DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
     boolean itfCall;
+    private final DesugaredLibraryAPIConverterEventConsumer eventConsumer;
 
     public APIConverterWrapperCfCodeProvider(
         AppView<?> appView,
         DexMethod forwardMethod,
         DexField wrapperField,
-        DesugaredLibraryAPIConverter converter,
-        boolean itfCall) {
+        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
+        boolean itfCall,
+        DesugaredLibraryAPIConverterEventConsumer eventConsumer) {
       //  Var wrapperField is null if should forward to receiver.
       super(appView, wrapperField == null ? forwardMethod.holder : wrapperField.holder);
       this.forwardMethod = forwardMethod;
       this.wrapperField = wrapperField;
-      this.converter = converter;
+      this.wrapperSynthesizor = wrapperSynthesizor;
       this.itfCall = itfCall;
+      this.eventConsumer = eventConsumer;
     }
 
     @Override
@@ -181,11 +180,12 @@
       int stackIndex = 1;
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, forwardMethod)) {
+        if (wrapperSynthesizor.shouldConvert(param, forwardMethod)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.ensureConversionMethod(param, vivifiedTypeFor(param), param),
+                  wrapperSynthesizor.ensureConversionMethod(
+                      param, vivifiedTypeFor(param), param, eventConsumer),
                   false));
         }
         if (param == factory.longType || param == factory.doubleType) {
@@ -201,12 +201,12 @@
       }
 
       DexType returnType = forwardMethod.proto.returnType;
-      if (shouldConvert(returnType, converter, forwardMethod)) {
+      if (wrapperSynthesizor.shouldConvert(returnType, forwardMethod)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
-                converter.ensureConversionMethod(
-                    returnType, returnType, vivifiedTypeFor(returnType)),
+                wrapperSynthesizor.ensureConversionMethod(
+                    returnType, returnType, vivifiedTypeFor(returnType), eventConsumer),
                 false));
         returnType = vivifiedTypeFor(returnType);
       }
@@ -282,6 +282,70 @@
     }
   }
 
+  public static class APIConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+    private final CfInvoke initialInvoke;
+    private final DexMethod returnConversion;
+    private final DexMethod[] parameterConversions;
+
+    public APIConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        CfInvoke initialInvoke,
+        DexMethod returnConversion,
+        DexMethod[] parameterConversions) {
+      super(appView, holder);
+      this.initialInvoke = initialInvoke;
+      this.returnConversion = returnConversion;
+      this.parameterConversions = parameterConversions;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexMethod invokedMethod = initialInvoke.getMethod();
+      DexMethod convertedMethod =
+          DesugaredLibraryAPIConverter.getConvertedAPI(
+              invokedMethod, returnConversion, parameterConversions, appView);
+
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      boolean isStatic = initialInvoke.getOpcode() == Opcodes.INVOKESTATIC;
+      if (!isStatic) {
+        instructions.add(new CfLoad(ValueType.fromDexType(invokedMethod.holder), 0));
+      }
+      int receiverShift = BooleanUtils.intValue(!isStatic);
+      int stackIndex = 0;
+      for (int i = 0; i < invokedMethod.getArity(); i++) {
+        DexType param = invokedMethod.getParameter(i);
+        instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex + receiverShift));
+        if (parameterConversions[i] != null) {
+          instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+        }
+        if (param == appView.dexItemFactory().longType
+            || param == appView.dexItemFactory().doubleType) {
+          stackIndex++;
+        }
+        stackIndex++;
+      }
+
+      // Actual call to converted value.
+      instructions.add(
+          new CfInvoke(initialInvoke.getOpcode(), convertedMethod, initialInvoke.isInterface()));
+
+      // Return conversion.
+      if (returnConversion != null) {
+        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
+      }
+
+      if (invokedMethod.getReturnType().isVoidType()) {
+        instructions.add(new CfReturnVoid());
+      } else {
+        instructions.add(new CfReturn(ValueType.fromDexType(invokedMethod.getReturnType())));
+      }
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
   public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
 
     DexField wrapperField;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
index 7f26a19..e1ba3b2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
@@ -36,12 +36,13 @@
   private final List<Pair<DexType, DexMethod>> extraDispatchCases;
 
   public EmulateInterfaceSyntheticCfCodeProvider(
+      DexType holder,
       DexType interfaceType,
       DexMethod companionMethod,
       DexMethod libraryMethod,
       List<Pair<DexType, DexMethod>> extraDispatchCases,
       AppView<?> appView) {
-    super(appView, interfaceType);
+    super(appView, holder);
     this.interfaceType = interfaceType;
     this.companionMethod = companionMethod;
     this.libraryMethod = libraryMethod;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
index 7a4fc78..c8cb4d6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -78,7 +78,6 @@
           .options()
           .reporter
           .info(KotlinMetadataDiagnostic.lambdaBackingNotFound(clazz.type, function.getName()));
-      return false;
     }
     return function.rewrite(visitorProvider.get()::visitFunction, backing, appView, namingLens);
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 8a4d337..e95253c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -54,6 +54,7 @@
     boolean keepKotlinMetadata =
         KeepClassInfo.isKotlinMetadataClassKept(
             appView.dexItemFactory(),
+            appView.options(),
             appView.appInfo()::definitionForWithoutExistenceAssert,
             enqueuer::getKeepInfo);
     // In the first round of tree shaking build up all metadata such that it can be traced later.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
index 256ee1e..065ff92 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -25,6 +25,7 @@
   private static final String SMAP_IDENTIFIER = "SMAP";
   private static final String SMAP_SECTION_START = "*S";
   private static final String SMAP_SECTION_KOTLIN_START = SMAP_SECTION_START + " Kotlin";
+  private static final String SMAP_SECTION_KOTLIN_DEBUG_START = SMAP_SECTION_START + " KotlinDebug";
   private static final String SMAP_FILES_IDENTIFIER = "*F";
   private static final String SMAP_LINES_IDENTIFIER = "*L";
   private static final String SMAP_END_IDENTIFIER = "*E";
@@ -84,13 +85,13 @@
       readUntil(terminator::equals, linesInBlock, callback);
     }
 
-    void readUntil(
+    String readUntil(
         Predicate<String> terminator,
         int linesInBlock,
         ThrowingConsumer<List<String>, KotlinSourceDebugExtensionParserException> callback)
         throws IOException, KotlinSourceDebugExtensionParserException {
       if (terminator.test(readLine)) {
-        return;
+        return readLine;
       }
       List<String> readStrings = new ArrayList<>();
       readStrings.add(readNextLine());
@@ -106,10 +107,11 @@
         }
         readStrings.add(readNextLine());
       }
-      if (readStrings.size() > 0 && !terminator.test(readStrings.get(0))) {
+      if (!readStrings.isEmpty() && !terminator.test(readStrings.get(0))) {
         throw new KotlinSourceDebugExtensionParserException(
             "Block size does not match linesInBlock = " + linesInBlock);
       }
+      return readStrings.isEmpty() ? null : readStrings.get(0);
     }
 
     @Override
@@ -132,14 +134,17 @@
     // + <file_id_i> <file_name_i>
     // <path_i>
     // *L
-    // <from>#<file>,<to>:<debug-line-position>
+    // <from>#<file>,<range>:<debug-line-position>
     // <from>#<file>:<debug-line-position>
     // *E <-- This is an error in versions prior to kotlin 1.5 and is not present in kotlin 1.5.
     // *S KotlinDebug
-    // ***
+    // + <file_id_i> <file_name_i>
+    // <path_i>
+    // *L
+    // <from>#<file>,<range>:<debug-line-position>
+    // <from>#<file>:<debug-line-position>
     // *E
     try (BufferedStringReader reader = new BufferedStringReader(annotationData)) {
-      ResultBuilder builder = new ResultBuilder();
       // Check for SMAP
       if (!reader.readExpectedLine(SMAP_IDENTIFIER)) {
         return null;
@@ -147,42 +152,67 @@
       if (reader.readUntil(SMAP_SECTION_KOTLIN_START).isEOF()) {
         return null;
       }
-      // At this point we should be parsing a kotlin source debug extension, so we will throw if we
-      // read an unexpected line.
-      reader.readExpectedLineOrThrow(SMAP_FILES_IDENTIFIER);
-      // Iterate over the files section with the format:
-      // + <file_number_i> <file_name_i>
-      // <file_path_i>
-      reader.readUntil(
-          SMAP_LINES_IDENTIFIER, 2, block -> addFileToBuilder(block.get(0), block.get(1), builder));
 
-      // Ensure that we read *L.
-      if (reader.isEOF()) {
-        throw new KotlinSourceDebugExtensionParserException(
-            "Unexpected EOF - no debug line positions");
+      StratumBuilder inlineePositions = new StratumBuilder();
+      StratumBuilder calleePositions = new StratumBuilder();
+
+      String terminatedLine = parseStratumContents(reader, inlineePositions);
+
+      // Read callee positions
+      if (terminatedLine.equals(SMAP_END_IDENTIFIER)) {
+        String nextLine = reader.readNextLine();
+        // If we read to the end then there is not KotlinDebug section which translates to no need
+        // for mapping the resulting positions obtained from the inlineePositions.
+        if (reader.isEOF()) {
+          assert nextLine == null;
+          return new Result(inlineePositions.segmentTree, calleePositions.segmentTree);
+        }
+        if (!nextLine.equals(SMAP_SECTION_KOTLIN_DEBUG_START)) {
+          return null;
+        }
+      } else if (!terminatedLine.equals(SMAP_SECTION_KOTLIN_DEBUG_START)) {
+        return null;
       }
-      // Iterate over the debug line number positions:
-      // <from>#<file>,<range>:<debug-line-position>
-      // or
-      // <from>#<file>:<debug-line-position>
-      reader.readUntil(
-          line -> line.equals(SMAP_END_IDENTIFIER) || line.startsWith(SMAP_SECTION_START),
-          1,
-          block -> addDebugEntryToBuilder(block.get(0), builder));
+      parseStratumContents(reader, calleePositions);
 
       // Ensure that we read the end section and it is terminated.
       if (reader.isEOF() && !reader.readLine.equals(SMAP_END_IDENTIFIER)) {
         throw new KotlinSourceDebugExtensionParserException(
             "Unexpected EOF when parsing SMAP debug entries");
       }
-
-      return builder.build();
+      return new Result(inlineePositions.segmentTree, calleePositions.segmentTree);
     } catch (IOException | KotlinSourceDebugExtensionParserException e) {
       return null;
     }
   }
 
-  private static void addFileToBuilder(String entryLine, String filePath, ResultBuilder builder)
+  private static String parseStratumContents(BufferedStringReader reader, StratumBuilder builder)
+      throws KotlinSourceDebugExtensionParserException, IOException {
+    // At this point we should be parsing a kotlin source debug extension, so we will throw if we
+    // read an unexpected line.
+    reader.readExpectedLineOrThrow(SMAP_FILES_IDENTIFIER);
+    // Iterate over the files section with the format:
+    // + <file_number_i> <file_name_i>
+    // <file_path_i>
+    reader.readUntil(
+        SMAP_LINES_IDENTIFIER, 2, block -> addFileToBuilder(block.get(0), block.get(1), builder));
+
+    // Ensure that we read *L.
+    if (reader.isEOF()) {
+      throw new KotlinSourceDebugExtensionParserException(
+          "Unexpected EOF - no debug line positions");
+    }
+    // Iterate over the debug line number positions:
+    // <from>#<file>,<range>:<debug-line-position>
+    // or
+    // <from>#<file>:<debug-line-position>
+    return reader.readUntil(
+        line -> line.equals(SMAP_END_IDENTIFIER) || line.startsWith(SMAP_SECTION_START),
+        1,
+        block -> addDebugEntryToBuilder(block.get(0), builder));
+  }
+
+  private static void addFileToBuilder(String entryLine, String filePath, StratumBuilder builder)
       throws KotlinSourceDebugExtensionParserException {
     // + <file_number_i> <file_name_i>
     // <file_path_i>
@@ -221,22 +251,30 @@
     return number;
   }
 
-  private static void addDebugEntryToBuilder(String debugEntry, ResultBuilder builder)
+  private static void addDebugEntryToBuilder(String debugEntry, StratumBuilder builder)
       throws KotlinSourceDebugExtensionParserException {
-    // <from>#<file>,<size>:<debug-line-position>
+    // <from>#<file>,<size>:<debug-line-position>,<size>
     // or
     // <from>#<file>:<debug-line-position>
     // All positions should define intervals for mappings.
     try {
+      int size = 1;
       int targetSplit = debugEntry.indexOf(':');
-      int target = Integer.parseInt(debugEntry.substring(targetSplit + 1));
+      int targetRangeStart = targetSplit + 1;
+      int targetRangeSeparator = debugEntry.indexOf(',', targetSplit);
+      int target;
+      if (targetRangeSeparator > -1) {
+        target = Integer.parseInt(debugEntry.substring(targetRangeStart, targetRangeSeparator));
+        size = Integer.parseInt(debugEntry.substring(targetRangeSeparator + 1));
+      } else {
+        target = Integer.parseInt(debugEntry.substring(targetRangeStart));
+      }
       String original = debugEntry.substring(0, targetSplit);
       int fileIndexSplit = original.indexOf('#');
       int originalStart = Integer.parseInt(original.substring(0, fileIndexSplit));
       // The range may have a different end than start.
       String fileAndEndRange = original.substring(fileIndexSplit + 1);
       int endRangeCharPosition = fileAndEndRange.indexOf(',');
-      int size = 1;
       if (endRangeCharPosition > -1) {
         // The file should be at least one number wide.
         assert endRangeCharPosition > 0;
@@ -260,29 +298,31 @@
 
   public static class Result {
 
-    private final SegmentTree<Position> segmentTree;
+    private final SegmentTree<Position> inlineePositions;
+    private final SegmentTree<Position> calleePositions;
 
-    public Result(SegmentTree<Position> segmentTree) {
-      this.segmentTree = segmentTree;
+    public Result(SegmentTree<Position> inlineePositions, SegmentTree<Position> calleePositions) {
+      this.inlineePositions = inlineePositions;
+      this.calleePositions = calleePositions;
     }
 
-    public Map.Entry<Integer, Position> lookup(int point) {
-      return segmentTree.findEntry(point);
+    public Map.Entry<Integer, Position> lookupInlinedPosition(int point) {
+      return inlineePositions.findEntry(point);
     }
 
-    public int size() {
-      return segmentTree.size();
+    public Map.Entry<Integer, Position> lookupCalleePosition(int point) {
+      return calleePositions.findEntry(point);
+    }
+
+    public int inlinePositionsCount() {
+      return inlineePositions.size();
     }
   }
 
-  public static class ResultBuilder {
+  public static class StratumBuilder {
 
     SegmentTree<Position> segmentTree = new SegmentTree<>(false);
     Map<Integer, Source> files = new HashMap<>();
-
-    public Result build() {
-      return new Result(segmentTree);
-    }
   }
 
   public static class Source {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 6ca2736..7e1c7df 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -360,7 +360,7 @@
       return;
     }
 
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethodOn(holder, method);
+    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOn(holder, method);
     if (resolutionResult.isSingleResolution()) {
       DexEncodedMethod resolvedMethod = resolutionResult.getSingleTarget();
       if (resolvedMethod.getReference() == method) {
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index c44d311..cc64864 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
@@ -106,7 +106,7 @@
       return true;
     }
 
-    ResolutionResult resolution = appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
+    MethodResolutionResult resolution = appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
     assert resolution != null;
 
     if (resolution.isSingleResolution()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 74737eb..1d0e10b 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.getInterfaceClassType;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.isCompanionClassType;
 import static com.android.tools.r8.utils.IterableUtils.fromMethod;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -21,7 +24,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -350,11 +352,10 @@
       Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames) {
     // If the class does not resolve, then check if it is a companion class for an interface on
     // the class path.
-    if (!InterfaceMethodRewriter.isCompanionClassType(type)) {
+    if (!isCompanionClassType(type)) {
       return;
     }
-    DexClass interfaceType =
-        appView.definitionFor(InterfaceMethodRewriter.getInterfaceClassType(type, factory));
+    DexClass interfaceType = appView.definitionFor(getInterfaceClassType(type, factory));
     if (interfaceType == null || !interfaceType.isClasspathClass()) {
       return;
     }
@@ -368,7 +369,7 @@
       MethodSignature signature = (MethodSignature) naming.getOriginalSignature();
       if (signature.name.startsWith(interfaceType.type.toSourceString())) {
         DexMethod defaultMethod =
-            InterfaceMethodRewriter.defaultAsMethodOfCompanionClass(
+            defaultAsMethodOfCompanionClass(
                 signature.toUnqualified().toDexMethod(factory, interfaceType.type), factory);
         assert defaultMethod.holder == type;
         defaultInterfaceMethodImplementationNames.put(
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index dd67f3a..ad17739 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
@@ -91,8 +92,6 @@
 
   private void doPublicize(ProgramDefinition definition) {
     definition.getAccessFlags().promoteToPublic();
-    keepInfo.mutate(
-        keepInfo -> keepInfo.unsetRequireAllowAccessModificationForRepackaging(definition));
   }
 
   private void publicizeType(DexType type) {
@@ -104,24 +103,12 @@
   }
 
   private void publicizeClass(DexProgramClass clazz) {
-    doPublicize(clazz);
+    if (appView.appInfo().isAccessModificationAllowed(clazz)) {
+      doPublicize(clazz);
+    }
 
     // Publicize fields.
-    clazz.forEachProgramField(
-        field -> {
-          DexEncodedField definition = field.getDefinition();
-          if (definition.isPublic()) {
-            return;
-          }
-          if (!appView.appInfo().isAccessModificationAllowed(field.getReference())) {
-            // TODO(b/131130038): Also do not publicize package-private and protected fields that
-            //  are kept.
-            if (definition.isPrivate()) {
-              return;
-            }
-          }
-          doPublicize(field);
-        });
+    clazz.forEachProgramField(this::publicizeField);
 
     // Publicize methods.
     Set<DexEncodedMethod> privateInstanceMethods = new LinkedHashSet<>();
@@ -145,6 +132,21 @@
     }
   }
 
+  private void publicizeField(ProgramField field) {
+    DexEncodedField definition = field.getDefinition();
+    if (definition.isPublic()) {
+      return;
+    }
+    if (!appView.appInfo().isAccessModificationAllowed(field)) {
+      // TODO(b/131130038): Also do not publicize package-private and protected fields that
+      //  are kept.
+      if (definition.isPrivate()) {
+        return;
+      }
+    }
+    doPublicize(field);
+  }
+
   private boolean publicizeMethod(ProgramMethod method) {
     MethodAccessFlags accessFlags = method.getAccessFlags();
     if (accessFlags.isPublic()) {
@@ -152,7 +154,7 @@
     }
     // If this method is mentioned in keep rules, do not transform (rule applications changed).
     DexEncodedMethod definition = method.getDefinition();
-    if (!appView.appInfo().isAccessModificationAllowed(method.getReference())) {
+    if (!appView.appInfo().isAccessModificationAllowed(method)) {
       // TODO(b/131130038): Also do not publicize package-private and protected methods that are
       //  kept.
       if (definition.isPrivate()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 0fdc499..8873add 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import java.util.IdentityHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index 6e1cc11..9d9a731 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -16,8 +16,8 @@
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
index 5a9e20c..745cf99 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 
 public class MemberRebindingUtils {
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
new file mode 100644
index 0000000..00ea1ac
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.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.optimize.argumentpropagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/** Optimization that propagates information about arguments from call sites to method entries. */
+// TODO(b/190154391): Add timing information for performance tracking.
+public class ArgumentPropagator {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  /**
+   * Collects information about arguments from call sites, meanwhile pruning redundant information.
+   *
+   * <p>The data held by this instance is incomplete and should not be used for optimization until
+   * processed by {@link ArgumentPropagatorOptimizationInfoPopulator}.
+   */
+  private ArgumentPropagatorCodeScanner codeScanner;
+
+  public ArgumentPropagator(AppView<AppInfoWithLiveness> appView) {
+    assert appView.enableWholeProgramOptimizations();
+    assert appView.options().isOptimizing();
+    assert appView.options().callSiteOptimizationOptions().isEnabled();
+    assert appView
+        .options()
+        .callSiteOptimizationOptions()
+        .isExperimentalArgumentPropagationEnabled();
+    this.appView = appView;
+  }
+
+  /**
+   * Called by {@link IRConverter} *before* the primary optimization pass to setup the scanner for
+   * collecting argument information from the code objects.
+   */
+  public void initializeCodeScanner() {
+    codeScanner = new ArgumentPropagatorCodeScanner(appView);
+
+    // Disable argument propagation for methods that should not be optimized.
+    ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+        ImmediateProgramSubtypingInfo.create(appView);
+    // TODO(b/190154391): Consider computing the strongly connected components and running this in
+    //  parallel for each scc.
+    new ArgumentPropagatorUnoptimizableMethods(
+            appView, immediateSubtypingInfo, codeScanner.getMethodStates())
+        .disableArgumentPropagationForUnoptimizableMethods(appView.appInfo().classes());
+  }
+
+  /** Called by {@link IRConverter} prior to finalizing methods. */
+  public void scan(ProgramMethod method, IRCode code, MethodProcessor methodProcessor) {
+    if (codeScanner != null) {
+      // TODO(b/190154391): Do we process synthetic methods using a OneTimeMethodProcessor
+      //  during the primary optimization pass?
+      assert methodProcessor.isPrimaryMethodProcessor();
+      codeScanner.scan(method, code);
+    } else {
+      assert !methodProcessor.isPrimaryMethodProcessor();
+    }
+  }
+
+  /**
+   * Called by {@link IRConverter} *after* the primary optimization pass to populate the parameter
+   * optimization info.
+   */
+  public void populateParameterOptimizationInfo(ExecutorService executorService)
+      throws ExecutionException {
+    // Unset the scanner since all code objects have been scanned at this point.
+    assert appView.isAllCodeProcessed();
+    MethodStateCollectionByReference codeScannerResult = codeScanner.getMethodStates();
+    codeScanner = null;
+
+    new ArgumentPropagatorOptimizationInfoPopulator(appView, codeScannerResult)
+        .populateOptimizationInfo(executorService);
+  }
+
+  /** Called by {@link IRConverter} to optimize method definitions. */
+  public void optimizeMethodParameters() {
+    // TODO(b/190154391): Remove parameters with constant values.
+    // TODO(b/190154391): Remove unused parameters by simulating they are constant.
+    // TODO(b/190154391): Strengthen the static type of parameters.
+    // TODO(b/190154391): If we learn that a method returns a constant, then consider changing its
+    //  return type to void.
+  }
+
+  /**
+   * Called by {@link IRConverter} to add all methods that require reprocessing to {@param
+   * postMethodProcessorBuilder}.
+   */
+  public void enqueueMethodsForProcessing(PostMethodProcessor.Builder postMethodProcessorBuilder) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramMethodMatching(
+          DexEncodedMethod::hasCode,
+          method -> {
+            CallSiteOptimizationInfo callSiteOptimizationInfo =
+                method.getDefinition().getCallSiteOptimizationInfo();
+            if (callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+              postMethodProcessorBuilder.add(method);
+            }
+          });
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
new file mode 100644
index 0000000..509079e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -0,0 +1,331 @@
+// 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 com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.AliasedValueConfiguration;
+import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeCustom;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.Value;
+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.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodStateOrUnknown;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReceiverParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Analyzes each {@link IRCode} during the primary optimization to collect information about the
+ * arguments passed to method parameters.
+ *
+ * <p>State pruning is applied on-the-fly to avoid storing redundant information.
+ */
+class ArgumentPropagatorCodeScanner {
+
+  private static AliasedValueConfiguration aliasedValueConfiguration =
+      AssumeAndCheckCastAliasedValueConfiguration.getInstance();
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  /**
+   * Maps each non-interface method to the upper most method in the super class chain with the same
+   * method signature. This only contains an entry for non-private virtual methods that override
+   * another method in the program.
+   */
+  private final Map<DexMethod, DexMethod> classMethodRoots;
+
+  /**
+   * The abstract program state for this optimization. Intuitively maps each parameter to its
+   * abstract value and dynamic type.
+   */
+  private final MethodStateCollectionByReference methodStates;
+
+  ArgumentPropagatorCodeScanner(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    this.classMethodRoots = computeClassMethodRoots();
+    this.methodStates = computeInitialMethodStates();
+  }
+
+  private Map<DexMethod, DexMethod> computeClassMethodRoots() {
+    // TODO(b/190154391): Group methods related by overriding to enable more effective pruning.
+    Map<DexMethod, DexMethod> roots = new IdentityHashMap<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      clazz.forEachProgramVirtualMethod(
+          method -> roots.put(method.getReference(), method.getReference()));
+    }
+    return roots;
+  }
+
+  private MethodStateCollectionByReference computeInitialMethodStates() {
+    // TODO(b/190154391): There is no need to track an abstract value for receivers; we only care
+    //  about the dynamic type for such parameters. Consider initializing the initial state to have
+    //  unknown abstract values for all receivers.
+    return MethodStateCollectionByReference.createConcurrent();
+  }
+
+  MethodStateCollectionByReference getMethodStates() {
+    return methodStates;
+  }
+
+  void scan(ProgramMethod method, IRCode code) {
+    for (Invoke invoke : code.<Invoke>instructions(Instruction::isInvoke)) {
+      if (invoke.isInvokeMethod()) {
+        scan(invoke.asInvokeMethod(), method);
+      } else if (invoke.isInvokeCustom()) {
+        scan(invoke.asInvokeCustom(), method);
+      }
+    }
+  }
+
+  private void scan(InvokeMethod invoke, ProgramMethod context) {
+    List<Value> arguments = invoke.arguments();
+    if (arguments.isEmpty()) {
+      // Nothing to propagate.
+      return;
+    }
+
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (invokedMethod.getHolderType().isArrayType()) {
+      // Nothing to propagate.
+      return;
+    }
+
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution();
+    if (resolutionResult == null) {
+      // Nothing to propagate; the invoke instruction fails.
+      return;
+    }
+
+    // TODO(b/190154391): Also bail out if the method is an unoptimizable program method.
+    if (!resolutionResult.getResolvedHolder().isProgramClass()) {
+      // Nothing to propagate; this could dispatch to a program method, but we cannot optimize
+      // methods that override non-program methods.
+      return;
+    }
+
+    ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
+    if (resolvedMethod.getDefinition().isLibraryMethodOverride().isPossiblyTrue()) {
+      assert resolvedMethod.getDefinition().isLibraryMethodOverride().isTrue();
+      // Nothing to propagate; we don't know anything about methods that can be called from outside
+      // the program.
+      return;
+    }
+
+    if (arguments.size() != resolvedMethod.getDefinition().getNumberOfArguments()
+        || invoke.isInvokeStatic() != resolvedMethod.getAccessFlags().isStatic()) {
+      // Nothing to propagate; the invoke instruction fails.
+      return;
+    }
+
+    // Find the method where to store the information about the arguments from this invoke.
+    // If the invoke may dispatch to more than one method, we intentionally do not compute all
+    // possible dispatch targets and propagate the information to these methods (this is expensive).
+    // Instead we record the information in one place and then later propagate the information to
+    // all dispatch targets.
+    DexMethod representativeMethodReference =
+        getRepresentativeForPolymorphicInvokeOrElse(
+            invoke, resolvedMethod, resolvedMethod.getReference());
+    methodStates.addMethodState(
+        appView,
+        representativeMethodReference,
+        () -> computeMethodState(invoke, resolvedMethod, context));
+  }
+
+  private MethodState computeMethodState(
+      InvokeMethod invoke, ProgramMethod resolvedMethod, ProgramMethod context) {
+    // If this invoke may target at most one method, then we compute a state that maps each
+    // parameter to the abstract value and dynamic type provided by this call site. Otherwise, we
+    // compute a polymorphic method state, which includes information about the receiver's dynamic
+    // type bounds.
+    boolean isPolymorphicInvoke =
+        getRepresentativeForPolymorphicInvokeOrElse(invoke, resolvedMethod, null) != null;
+    return isPolymorphicInvoke
+        ? computePolymorphicMethodState(invoke.asInvokeMethodWithReceiver(), context)
+        : computeMonomorphicMethodState(invoke, context);
+  }
+
+  // TODO(b/190154391): Add a strategy that widens the dynamic receiver type to allow easily
+  //  experimenting with the performance/size trade-off between precise/imprecise handling of
+  //  dynamic dispatch.
+  private MethodState computePolymorphicMethodState(
+      InvokeMethodWithReceiver invoke, ProgramMethod context) {
+    DynamicType dynamicReceiverType = invoke.getReceiver().getDynamicType(appView);
+    ConcretePolymorphicMethodState methodState =
+        new ConcretePolymorphicMethodState(
+            dynamicReceiverType,
+            computeMonomorphicMethodState(invoke, context, dynamicReceiverType));
+    // TODO(b/190154391): If the receiver type is effectively unknown, and the computed monomorphic
+    //  method state is also unknown (i.e., we have "unknown receiver type" -> "unknown method
+    //  state"), then return the canonicalized UnknownMethodState instance instead.
+    return methodState;
+  }
+
+  private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
+      InvokeMethod invoke, ProgramMethod context) {
+    return computeMonomorphicMethodState(
+        invoke,
+        context,
+        invoke.isInvokeMethodWithReceiver()
+            ? invoke.getFirstArgument().getDynamicType(appView)
+            : null);
+  }
+
+  private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
+      InvokeMethod invoke, ProgramMethod context, DynamicType dynamicReceiverType) {
+    List<ParameterState> parameterStates = new ArrayList<>(invoke.arguments().size());
+
+    int argumentIndex = 0;
+    if (invoke.isInvokeMethodWithReceiver()) {
+      assert dynamicReceiverType != null;
+      parameterStates.add(
+          computeParameterStateForReceiver(
+              invoke.asInvokeMethodWithReceiver(), dynamicReceiverType));
+      argumentIndex++;
+    }
+
+    for (; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+      parameterStates.add(
+          computeParameterStateForNonReceiver(
+              invoke, argumentIndex, invoke.getArgument(argumentIndex), context));
+    }
+
+    // If all parameter states are unknown, then return a canonicalized unknown method state that
+    // has this property.
+    if (Iterables.all(parameterStates, ParameterState::isUnknown)) {
+      return MethodState.unknown();
+    }
+
+    return new ConcreteMonomorphicMethodState(parameterStates);
+  }
+
+  // For receivers there is not much point in trying to track an abstract value. Therefore we only
+  // track the dynamic type for receivers.
+  // TODO(b/190154391): Consider validating the above hypothesis by using
+  //  computeParameterStateForNonReceiver() for receivers.
+  private ParameterState computeParameterStateForReceiver(
+      InvokeMethodWithReceiver invoke, DynamicType dynamicReceiverType) {
+    ClassTypeElement staticReceiverType =
+        invoke
+            .getInvokedMethod()
+            .getHolderType()
+            .toTypeElement(appView)
+            .asClassType()
+            .asMeetWithNotNull();
+    return dynamicReceiverType.isTrivial(staticReceiverType)
+        ? ParameterState.unknown()
+        : new ConcreteReceiverParameterState(dynamicReceiverType);
+  }
+
+  private ParameterState computeParameterStateForNonReceiver(
+      InvokeMethod invoke, int argumentIndex, Value argument, ProgramMethod context) {
+    Value argumentRoot = argument.getAliasedValue(aliasedValueConfiguration);
+    TypeElement parameterType =
+        invoke
+            .getInvokedMethod()
+            .getArgumentType(argumentIndex, invoke.isInvokeStatic())
+            .toTypeElement(appView);
+
+    // If the value is an argument of the enclosing method, then clearly we have no information
+    // about its abstract value. Instead of treating this as having an unknown runtime value, we
+    // instead record a flow constraint that specifies that all values that flow into the parameter
+    // of this enclosing method also flows into the corresponding parameter of the methods
+    // potentially called from this invoke instruction.
+    if (argumentRoot.isArgument()) {
+      MethodParameter forwardedParameter =
+          new MethodParameter(
+              context.getReference(), argumentRoot.getDefinition().asArgument().getIndex());
+      if (parameterType.isClassType()) {
+        return new ConcreteClassTypeParameterState(forwardedParameter);
+      } else if (parameterType.isArrayType()) {
+        return new ConcreteArrayTypeParameterState(forwardedParameter);
+      } else {
+        assert parameterType.isPrimitiveType();
+        return new ConcretePrimitiveTypeParameterState(forwardedParameter);
+      }
+    }
+
+    // Only track the nullability for array types.
+    if (parameterType.isArrayType()) {
+      Nullability nullability = argument.getType().nullability();
+      return nullability.isMaybeNull()
+          ? ParameterState.unknown()
+          : new ConcreteArrayTypeParameterState(nullability);
+    }
+
+    AbstractValue abstractValue = argument.getAbstractValue(appView, context);
+
+    // For class types, we track both the abstract value and the dynamic type. If both are unknown,
+    // then use UnknownParameterState.
+    if (parameterType.isClassType()) {
+      DynamicType dynamicType = argument.getDynamicType(appView);
+      return abstractValue.isUnknown() && dynamicType.isTrivial(parameterType)
+          ? ParameterState.unknown()
+          : new ConcreteClassTypeParameterState(abstractValue, dynamicType);
+    }
+
+    // For primitive types, we only track the abstract value, thus if the abstract value is unknown,
+    // we use UnknownParameterState.
+    assert parameterType.isPrimitiveType();
+    return abstractValue.isUnknown()
+        ? ParameterState.unknown()
+        : new ConcretePrimitiveTypeParameterState(abstractValue);
+  }
+
+  private DexMethod getRepresentativeForPolymorphicInvokeOrElse(
+      InvokeMethod invoke, ProgramMethod resolvedMethod, DexMethod defaultValue) {
+    DexMethod resolvedMethodReference = resolvedMethod.getReference();
+    if (invoke.isInvokeInterface()) {
+      return resolvedMethodReference;
+    }
+    DexMethod rootMethod = classMethodRoots.get(resolvedMethodReference);
+    if (rootMethod != null) {
+      assert invoke.isInvokeVirtual();
+      return rootMethod;
+    }
+    return defaultValue;
+  }
+
+  private void scan(InvokeCustom invoke, ProgramMethod context) {
+    // If the bootstrap method is program declared it will be called. The call is with runtime
+    // provided arguments so ensure that the argument information is unknown.
+    DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
+    SingleResolutionResult resolution =
+        appView
+            .appInfo()
+            .resolveMethod(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
+            .asSingleResolution();
+    if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+      methodStates.set(resolution.getResolvedProgramMethod(), UnknownMethodState.get());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorIROptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorIROptimizer.java
new file mode 100644
index 0000000..6f7ec2c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorIROptimizer.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.optimize.argumentpropagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.ConstNumber;
+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.Value;
+import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public class ArgumentPropagatorIROptimizer {
+
+  /**
+   * Applies the (non-trivial) argument information to the given piece of code.
+   *
+   * <p>This involves replacing usages of {@link Argument} instructions by constants, and injecting
+   * {@link Assume} instructions when non-trivial information is known about non-constant arguments
+   * such as their nullability, dynamic type, interval, etc.
+   */
+  public static void optimize(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      ConcreteCallSiteOptimizationInfo optimizationInfo) {
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    List<Assume> assumeInstructions = new LinkedList<>();
+    List<Instruction> instructionsToAdd = new LinkedList<>();
+    InstructionListIterator iterator = code.entryBlock().listIterator(code);
+    while (iterator.hasNext()) {
+      Argument argument = iterator.next().asArgument();
+      if (argument == null) {
+        break;
+      }
+
+      Value argumentValue = argument.asArgument().outValue();
+      if (argumentValue.hasLocalInfo()) {
+        continue;
+      }
+
+      // If the argument is constant, then materialize the constant and replace all uses of the
+      // argument by the newly materialized constant.
+      // TODO(b/190154391): Constant arguments should instead be removed from the enclosing method
+      //  signature, and this should assert that the argument does not have a single materializable
+      //  value.
+      AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argument.getIndex());
+      if (abstractValue.isSingleValue()) {
+        assert appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
+        SingleValue singleValue = abstractValue.asSingleValue();
+        if (singleValue.isMaterializableInContext(appView, code.context())) {
+          Instruction replacement =
+              singleValue.createMaterializingInstruction(appView, code, argument);
+          replacement.setPosition(argument.getPosition());
+          affectedValues.addAll(argumentValue.affectedValues());
+          argumentValue.replaceUsers(replacement.outValue());
+          instructionsToAdd.add(replacement);
+          continue;
+        }
+      }
+
+      // If a dynamic type is known for the argument, then inject an Assume instruction with the
+      // dynamic type information.
+      // TODO(b/190154391): This should also materialize dynamic lower bound information.
+      // TODO(b/190154391) This should also materialize the nullability of array arguments.
+      if (argumentValue.getType().isReferenceType()) {
+        TypeElement dynamicUpperBoundType =
+            optimizationInfo.getDynamicUpperBoundType(argument.getIndex());
+        if (dynamicUpperBoundType == null) {
+          continue;
+        }
+        if (dynamicUpperBoundType.isDefinitelyNull()) {
+          ConstNumber nullInstruction = code.createConstNull();
+          nullInstruction.setPosition(argument.getPosition());
+          affectedValues.addAll(argumentValue.affectedValues());
+          argumentValue.replaceUsers(nullInstruction.outValue());
+          instructionsToAdd.add(nullInstruction);
+          continue;
+        }
+        Value specializedArg;
+        if (dynamicUpperBoundType.strictlyLessThan(argumentValue.getType(), appView)) {
+          specializedArg = code.createValue(argumentValue.getType());
+          affectedValues.addAll(argumentValue.affectedValues());
+          argumentValue.replaceUsers(specializedArg);
+          Assume assumeType =
+              Assume.createAssumeDynamicTypeInstruction(
+                  dynamicUpperBoundType, null, specializedArg, argumentValue, argument, appView);
+          assumeType.setPosition(argument.getPosition());
+          assumeInstructions.add(assumeType);
+        } else {
+          specializedArg = argumentValue;
+        }
+        assert specializedArg != null && specializedArg.getType().isReferenceType();
+        if (dynamicUpperBoundType.isDefinitelyNotNull()) {
+          // If we already knew `arg` is never null, e.g., receiver, skip adding non-null.
+          if (!specializedArg.getType().isDefinitelyNotNull()) {
+            Value nonNullArg =
+                code.createValue(specializedArg.getType().asReferenceType().asMeetWithNotNull());
+            affectedValues.addAll(specializedArg.affectedValues());
+            specializedArg.replaceUsers(nonNullArg);
+            Assume assumeNotNull =
+                Assume.createAssumeNonNullInstruction(
+                    nonNullArg, specializedArg, argument, appView);
+            assumeNotNull.setPosition(argument.getPosition());
+            assumeInstructions.add(assumeNotNull);
+          }
+        }
+      }
+    }
+
+    // Insert the newly created instructions after the last Argument instruction.
+    assert !iterator.peekPrevious().isArgument();
+    iterator.previous();
+    assert iterator.peekPrevious().isArgument();
+    assumeInstructions.forEach(iterator::add);
+
+    // TODO(b/190154391): Can update method signature and save more on call sites.
+    instructionsToAdd.forEach(iterator::add);
+
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+  }
+}
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
new file mode 100644
index 0000000..13455ba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -0,0 +1,181 @@
+// 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 com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.InParameterFlowPropagator;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator;
+import com.android.tools.r8.optimize.argumentpropagation.propagation.VirtualDispatchMethodArgumentPropagator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Propagates the argument flow information collected by the {@link ArgumentPropagatorCodeScanner}.
+ * This is needed to propagate argument information from call sites to all possible dispatch
+ * targets.
+ */
+public class ArgumentPropagatorOptimizationInfoPopulator {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final MethodStateCollectionByReference methodStates;
+
+  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+  private final List<Set<DexProgramClass>> stronglyConnectedComponents;
+
+  ArgumentPropagatorOptimizationInfoPopulator(
+      AppView<AppInfoWithLiveness> appView, MethodStateCollectionByReference methodStates) {
+    this.appView = appView;
+    this.methodStates = methodStates;
+
+    ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+        ImmediateProgramSubtypingInfo.create(appView);
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
+    this.stronglyConnectedComponents =
+        computeStronglyConnectedComponents(appView, immediateSubtypingInfo);
+  }
+
+  /**
+   * Computes the strongly connected components in the program class hierarchy (where extends and
+   * implements edges are treated as bidirectional).
+   *
+   * <p>All strongly connected components can be processed in parallel.
+   */
+  private static List<Set<DexProgramClass>> computeStronglyConnectedComponents(
+      AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+    Set<DexProgramClass> seen = Sets.newIdentityHashSet();
+    List<Set<DexProgramClass>> stronglyConnectedComponents = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (seen.contains(clazz)) {
+        continue;
+      }
+      Set<DexProgramClass> stronglyConnectedComponent =
+          computeStronglyConnectedComponent(clazz, immediateSubtypingInfo);
+      stronglyConnectedComponents.add(stronglyConnectedComponent);
+      seen.addAll(stronglyConnectedComponent);
+    }
+    return stronglyConnectedComponents;
+  }
+
+  private static Set<DexProgramClass> computeStronglyConnectedComponent(
+      DexProgramClass clazz, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(clazz);
+    while (worklist.hasNext()) {
+      DexProgramClass current = worklist.next();
+      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+          current,
+          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
+          (supertype, superclass) -> worklist.addIfNotSeen(superclass.asProgramClass()));
+      worklist.addIfNotSeen(immediateSubtypingInfo.getSubclasses(current));
+    }
+    return worklist.getSeenSet();
+  }
+
+  /**
+   * Computes an over-approximation of each parameter's value and type and stores the result in
+   * {@link com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}.
+   */
+  void populateOptimizationInfo(ExecutorService executorService) throws ExecutionException {
+    // TODO(b/190154391): Propagate argument information to handle virtual dispatch.
+    // TODO(b/190154391): To deal with arguments that are themselves passed as arguments to invoke
+    //  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.
+    ThreadUtils.processItems(
+        stronglyConnectedComponents, this::processStronglyConnectedComponent, executorService);
+
+    // Solve the parameter flow constraints.
+    new InParameterFlowPropagator(appView, methodStates).run(executorService);
+
+    // The information stored on each method is now sound, and can be used as optimization info.
+    setOptimizationInfo(executorService);
+
+    assert methodStates.isEmpty();
+  }
+
+  private void processStronglyConnectedComponent(Set<DexProgramClass> stronglyConnectedComponent) {
+    // Invoke instructions that target interface methods may dispatch to methods that are not
+    // defined on a subclass of the interface method holder.
+    //
+    // Example: Calling I.m() will dispatch to A.m(), but A is not a subtype of I.
+    //
+    //   class A { public void m() {} }
+    //   interface I { void m(); }
+    //   class B extends A implements I {}
+    //
+    // To handle this we first propagate any argument information stored for I.m() to A.m() by doing
+    // a top-down traversal over the interfaces in the strongly connected component.
+    new InterfaceMethodArgumentPropagator(appView, immediateSubtypingInfo, methodStates)
+        .run(stronglyConnectedComponent);
+
+    // Now all the argument information for a given method is guaranteed to be stored on a supertype
+    // of the method's holder. All that remains is to propagate the information downwards in the
+    // class hierarchy to propagate the argument information for a non-private virtual method to its
+    // overrides.
+    new VirtualDispatchMethodArgumentPropagator(appView, immediateSubtypingInfo, methodStates)
+        .run(stronglyConnectedComponent);
+  }
+
+  private void setOptimizationInfo(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        appView.appInfo().classes(), this::setOptimizationInfo, executorService);
+  }
+
+  private void setOptimizationInfo(DexProgramClass clazz) {
+    clazz.forEachProgramMethod(this::setOptimizationInfo);
+  }
+
+  private void setOptimizationInfo(ProgramMethod method) {
+    MethodState methodState = methodStates.remove(method);
+    if (methodState.isBottom()) {
+      // TODO(b/190154391): This should only happen if the method is never called. Consider removing
+      //  the method in this case.
+      return;
+    }
+
+    if (methodState.isUnknown()) {
+      // Nothing is known about the arguments to this method.
+      return;
+    }
+
+    ConcreteMethodState concreteMethodState = methodState.asConcrete();
+    if (concreteMethodState.isPolymorphic()) {
+      assert false;
+      return;
+    }
+
+    ConcreteMonomorphicMethodState monomorphicMethodState = concreteMethodState.asMonomorphic();
+    assert monomorphicMethodState.getParameterStates().stream()
+        .filter(ParameterState::isConcrete)
+        .map(ParameterState::asConcrete)
+        .noneMatch(ConcreteParameterState::hasInParameters);
+
+    method
+        .getDefinition()
+        .joinCallSiteOptimizationInfo(
+            ConcreteCallSiteOptimizationInfo.fromMethodState(
+                appView, method, monomorphicMethodState),
+            appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
new file mode 100644
index 0000000..9a478bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorUnoptimizableMethods.java
@@ -0,0 +1,245 @@
+// 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.MapUtils.ignoreKey;
+
+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.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.UnknownMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class ArgumentPropagatorUnoptimizableMethods {
+
+  private static final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+  private final MethodStateCollectionByReference methodStates;
+
+  public ArgumentPropagatorUnoptimizableMethods(
+      AppView<AppInfoWithLiveness> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      MethodStateCollectionByReference methodStates) {
+    this.appView = appView;
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
+    this.methodStates = methodStates;
+  }
+
+  // TODO(b/190154391): Consider if we should bail out for classes that inherit from a missing
+  //  class.
+  public void disableArgumentPropagationForUnoptimizableMethods(
+      Collection<DexProgramClass> stronglyConnectedComponent) {
+    ProgramMethodSet unoptimizableClassRootMethods = ProgramMethodSet.create();
+    ProgramMethodSet unoptimizableInterfaceRootMethods = ProgramMethodSet.create();
+    forEachUnoptimizableMethod(
+        stronglyConnectedComponent,
+        method -> {
+          if (method.getDefinition().belongsToVirtualPool()
+              && !method.getHolder().isFinal()
+              && !method.getAccessFlags().isFinal()) {
+            if (method.getHolder().isInterface()) {
+              unoptimizableInterfaceRootMethods.add(method);
+            } else {
+              unoptimizableClassRootMethods.add(method);
+            }
+          } else {
+            disableArgumentPropagationForMethod(method);
+          }
+        });
+
+    // Disable argument propagation for all overrides of the root methods. Since interface methods
+    // may be implemented by classes that are not a subtype of the interface that declares the
+    // interface method, we first mark the interface method overrides on such classes as ineligible
+    // for argument propagation.
+    if (!unoptimizableInterfaceRootMethods.isEmpty()) {
+      new UnoptimizableInterfaceMethodPropagator(
+              unoptimizableClassRootMethods, unoptimizableInterfaceRootMethods)
+          .run(stronglyConnectedComponent);
+    }
+
+    // At this point we can mark all overrides by a simple top-down traversal over the class
+    // hierarchy.
+    new UnoptimizableClassMethodPropagator(
+            unoptimizableClassRootMethods, unoptimizableInterfaceRootMethods)
+        .run(stronglyConnectedComponent);
+  }
+
+  private void disableArgumentPropagationForMethod(ProgramMethod method) {
+    methodStates.set(method, UnknownMethodState.get());
+  }
+
+  private void forEachUnoptimizableMethod(
+      Collection<DexProgramClass> stronglyConnectedComponent, Consumer<ProgramMethod> consumer) {
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    InternalOptions options = appView.options();
+    for (DexProgramClass clazz : stronglyConnectedComponent) {
+      clazz.forEachProgramMethod(
+          method -> {
+            assert !method.getDefinition().isLibraryMethodOverride().isUnknown();
+            if (method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
+                || appInfo.isMethodTargetedByInvokeDynamic(method)
+                || !appInfo
+                    .getKeepInfo()
+                    .getMethodInfo(method)
+                    .isArgumentPropagationAllowed(options)) {
+              consumer.accept(method);
+            }
+          });
+    }
+  }
+
+  private class UnoptimizableInterfaceMethodPropagator
+      extends DepthFirstTopDownClassHierarchyTraversal {
+
+    private final ProgramMethodSet unoptimizableClassRootMethods;
+    private final Map<DexProgramClass, Set<Wrapper<DexMethod>>> unoptimizableInterfaceMethods =
+        new IdentityHashMap<>();
+
+    UnoptimizableInterfaceMethodPropagator(
+        ProgramMethodSet unoptimizableClassRootMethods,
+        ProgramMethodSet unoptimizableInterfaceRootMethods) {
+      super(
+          ArgumentPropagatorUnoptimizableMethods.this.appView,
+          ArgumentPropagatorUnoptimizableMethods.this.immediateSubtypingInfo);
+      this.unoptimizableClassRootMethods = unoptimizableClassRootMethods;
+      unoptimizableInterfaceRootMethods.forEach(this::addUnoptimizableRootMethod);
+    }
+
+    private void addUnoptimizableRootMethod(ProgramMethod method) {
+      unoptimizableInterfaceMethods
+          .computeIfAbsent(method.getHolder(), ignoreKey(Sets::newIdentityHashSet))
+          .add(equivalence.wrap(method.getReference()));
+    }
+
+    @Override
+    public void visit(DexProgramClass clazz) {
+      Set<Wrapper<DexMethod>> unoptimizableInterfaceMethodsForClass =
+          unoptimizableInterfaceMethods.computeIfAbsent(clazz, ignoreKey(Sets::newIdentityHashSet));
+
+      // Add the unoptimizable interface methods from the parent interfaces.
+      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+          clazz,
+          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
+          (supertype, superclass) ->
+              unoptimizableInterfaceMethodsForClass.addAll(
+                  unoptimizableInterfaceMethods.get(superclass.asProgramClass())));
+
+      // Propagate the unoptimizable interface methods of this interface to all immediate
+      // (non-interface) subclasses.
+      for (DexProgramClass implementer : immediateSubtypingInfo.getSubclasses(clazz)) {
+        if (implementer.isInterface()) {
+          continue;
+        }
+
+        for (Wrapper<DexMethod> unoptimizableInterfaceMethod :
+            unoptimizableInterfaceMethodsForClass) {
+          SingleResolutionResult resolutionResult =
+              appView
+                  .appInfo()
+                  .resolveMethodOnClass(unoptimizableInterfaceMethod.get(), implementer)
+                  .asSingleResolution();
+          if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
+            continue;
+          }
+
+          ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
+          if (resolvedMethod.getHolder().isInterface()
+              || resolvedMethod.getHolder() == implementer) {
+            continue;
+          }
+
+          unoptimizableClassRootMethods.add(resolvedMethod);
+        }
+      }
+    }
+
+    @Override
+    public void prune(DexProgramClass clazz) {
+      unoptimizableInterfaceMethods.remove(clazz);
+    }
+
+    @Override
+    public boolean isRoot(DexProgramClass clazz) {
+      return clazz.isInterface() && super.isRoot(clazz);
+    }
+
+    @Override
+    public void forEachSubClass(DexProgramClass clazz, Consumer<DexProgramClass> consumer) {
+      for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(clazz)) {
+        if (subclass.isInterface()) {
+          consumer.accept(subclass);
+        }
+      }
+    }
+  }
+
+  private class UnoptimizableClassMethodPropagator
+      extends DepthFirstTopDownClassHierarchyTraversal {
+
+    private final Map<DexProgramClass, DexMethodSignatureSet> unoptimizableMethods =
+        new IdentityHashMap<>();
+
+    UnoptimizableClassMethodPropagator(
+        ProgramMethodSet unoptimizableClassRootMethods,
+        ProgramMethodSet unoptimizableInterfaceRootMethods) {
+      super(
+          ArgumentPropagatorUnoptimizableMethods.this.appView,
+          ArgumentPropagatorUnoptimizableMethods.this.immediateSubtypingInfo);
+      unoptimizableClassRootMethods.forEach(this::addUnoptimizableRootMethod);
+      unoptimizableInterfaceRootMethods.forEach(this::addUnoptimizableRootMethod);
+    }
+
+    private void addUnoptimizableRootMethod(ProgramMethod method) {
+      unoptimizableMethods
+          .computeIfAbsent(method.getHolder(), ignoreKey(DexMethodSignatureSet::create))
+          .add(method);
+    }
+
+    @Override
+    public void visit(DexProgramClass clazz) {
+      DexMethodSignatureSet unoptimizableMethodsForClass =
+          unoptimizableMethods.computeIfAbsent(clazz, ignoreKey(DexMethodSignatureSet::create));
+
+      // Add the unoptimizable methods from the parent classes.
+      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+          clazz,
+          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
+          (supertype, superclass) ->
+              unoptimizableMethodsForClass.addAll(
+                  unoptimizableMethods.get(superclass.asProgramClass())));
+
+      // Disable argument propagation for the unoptimizable methods of this class.
+      clazz.forEachProgramVirtualMethod(
+          method -> {
+            if (unoptimizableMethodsForClass.contains(method)) {
+              disableArgumentPropagationForMethod(method);
+            }
+          });
+    }
+
+    @Override
+    public void prune(DexProgramClass clazz) {
+      unoptimizableMethods.remove(clazz);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
new file mode 100644
index 0000000..fea97a2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomMethodState.java
@@ -0,0 +1,36 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Supplier;
+
+public class BottomMethodState extends MethodStateBase {
+
+  private static final BottomMethodState INSTANCE = new BottomMethodState();
+
+  private BottomMethodState() {}
+
+  public static BottomMethodState get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isBottom() {
+    return true;
+  }
+
+  @Override
+  public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) {
+    return methodState;
+  }
+
+  @Override
+  public MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) {
+    return methodStateSupplier.get();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
new file mode 100644
index 0000000..e7533e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeParameterState.java
@@ -0,0 +1,62 @@
+// 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.codescanner;
+
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.utils.Action;
+
+public class ConcreteArrayTypeParameterState extends ConcreteParameterState {
+
+  private Nullability nullability;
+
+  public ConcreteArrayTypeParameterState(MethodParameter inParameter) {
+    super(inParameter);
+    this.nullability = Nullability.bottom();
+  }
+
+  public ConcreteArrayTypeParameterState(Nullability nullability) {
+    assert !nullability.isMaybeNull() : "Must use UnknownParameterState instead";
+    this.nullability = nullability;
+  }
+
+  public ParameterState mutableJoin(
+      ConcreteArrayTypeParameterState parameterState, Action onChangedAction) {
+    assert !nullability.isMaybeNull();
+    assert !parameterState.nullability.isMaybeNull();
+    boolean inParametersChanged = mutableJoinInParameters(parameterState);
+    if (widenInParameters()) {
+      return unknown();
+    }
+    if (inParametersChanged) {
+      onChangedAction.execute();
+    }
+    return this;
+  }
+
+  @Override
+  public AbstractValue getAbstractValue() {
+    return AbstractValue.unknown();
+  }
+
+  @Override
+  public ConcreteParameterStateKind getKind() {
+    return ConcreteParameterStateKind.ARRAY;
+  }
+
+  public Nullability getNullability() {
+    return nullability;
+  }
+
+  @Override
+  public boolean isArrayParameter() {
+    return true;
+  }
+
+  @Override
+  public ConcreteArrayTypeParameterState asArrayParameter() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
new file mode 100644
index 0000000..3c59a07
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -0,0 +1,84 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public class ConcreteClassTypeParameterState extends ConcreteParameterState {
+
+  private AbstractValue abstractValue;
+  private DynamicType dynamicType;
+
+  public ConcreteClassTypeParameterState(MethodParameter inParameter) {
+    super(inParameter);
+    this.abstractValue = AbstractValue.bottom();
+    this.dynamicType = DynamicType.bottom();
+  }
+
+  public ConcreteClassTypeParameterState(AbstractValue abstractValue, DynamicType dynamicType) {
+    this.abstractValue = abstractValue;
+    this.dynamicType = dynamicType;
+  }
+
+  public ParameterState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteClassTypeParameterState parameterState,
+      Action onChangedAction) {
+    boolean allowNullOrAbstractValue = true;
+    boolean allowNonConstantNumbers = false;
+    AbstractValue oldAbstractValue = abstractValue;
+    abstractValue =
+        abstractValue.join(
+            parameterState.abstractValue,
+            appView.abstractValueFactory(),
+            allowNullOrAbstractValue,
+            allowNonConstantNumbers);
+    // TODO(b/190154391): Join the dynamic types using SubtypingInfo.
+    // TODO(b/190154391): Take in the static type as an argument, and unset the dynamic type if it
+    //  equals the static type.
+    DynamicType oldDynamicType = dynamicType;
+    dynamicType =
+        dynamicType.equals(parameterState.dynamicType) ? dynamicType : DynamicType.unknown();
+    if (abstractValue.isUnknown() && dynamicType.isUnknown()) {
+      return unknown();
+    }
+    boolean inParametersChanged = mutableJoinInParameters(parameterState);
+    if (widenInParameters()) {
+      return unknown();
+    }
+    if (abstractValue != oldAbstractValue || dynamicType != oldDynamicType || inParametersChanged) {
+      onChangedAction.execute();
+    }
+    return this;
+  }
+
+  @Override
+  public AbstractValue getAbstractValue() {
+    return abstractValue;
+  }
+
+  public DynamicType getDynamicType() {
+    return dynamicType;
+  }
+
+  @Override
+  public ConcreteParameterStateKind getKind() {
+    return ConcreteParameterStateKind.CLASS;
+  }
+
+  @Override
+  public boolean isClassParameter() {
+    return true;
+  }
+
+  @Override
+  public ConcreteClassTypeParameterState asClassParameter() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
new file mode 100644
index 0000000..6aae75f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMethodState.java
@@ -0,0 +1,59 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Supplier;
+
+public abstract class ConcreteMethodState extends MethodStateBase {
+
+  @Override
+  public boolean isConcrete() {
+    return true;
+  }
+
+  @Override
+  public ConcreteMethodState asConcrete() {
+    return this;
+  }
+
+  public boolean isPolymorphic() {
+    return false;
+  }
+
+  public ConcretePolymorphicMethodState asPolymorphic() {
+    return null;
+  }
+
+  @Override
+  public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) {
+    if (methodState.isBottom()) {
+      return this;
+    }
+    if (methodState.isUnknown()) {
+      return methodState;
+    }
+    return mutableJoin(appView, methodState.asConcrete());
+  }
+
+  @Override
+  public MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) {
+    return mutableJoin(appView, methodStateSupplier.get());
+  }
+
+  private MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ConcreteMethodState methodState) {
+    if (isMonomorphic() && methodState.isMonomorphic()) {
+      return asMonomorphic().mutableJoin(appView, methodState.asMonomorphic());
+    }
+    if (isPolymorphic() && methodState.isPolymorphic()) {
+      return asPolymorphic().mutableJoin(appView, methodState.asPolymorphic());
+    }
+    assert false;
+    return unknown();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
new file mode 100644
index 0000000..fc8fe25
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
@@ -0,0 +1,67 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Iterables;
+import java.util.List;
+
+public class ConcreteMonomorphicMethodState extends ConcreteMethodState
+    implements ConcreteMonomorphicMethodStateOrUnknown {
+
+  List<ParameterState> parameterStates;
+
+  public ConcreteMonomorphicMethodState(List<ParameterState> parameterStates) {
+    assert Iterables.any(parameterStates, parameterState -> !parameterState.isUnknown())
+        : "Must use UnknownMethodState instead";
+    this.parameterStates = parameterStates;
+  }
+
+  public ParameterState getParameterState(int index) {
+    return parameterStates.get(index);
+  }
+
+  public List<ParameterState> getParameterStates() {
+    return parameterStates;
+  }
+
+  public ConcreteMonomorphicMethodStateOrUnknown mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ConcreteMonomorphicMethodState methodState) {
+    if (size() != methodState.size()) {
+      assert false;
+      return unknown();
+    }
+
+    for (int i = 0; i < size(); i++) {
+      ParameterState parameterState = parameterStates.get(i);
+      ParameterState otherParameterState = methodState.parameterStates.get(i);
+      parameterStates.set(i, parameterState.mutableJoin(appView, otherParameterState));
+    }
+
+    if (Iterables.all(parameterStates, ParameterState::isUnknown)) {
+      return unknown();
+    }
+    return this;
+  }
+
+  @Override
+  public boolean isMonomorphic() {
+    return true;
+  }
+
+  @Override
+  public ConcreteMonomorphicMethodState asMonomorphic() {
+    return this;
+  }
+
+  public void setParameterState(int index, ParameterState parameterState) {
+    parameterStates.set(index, parameterState);
+  }
+
+  public int size() {
+    return parameterStates.size();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrUnknown.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrUnknown.java
new file mode 100644
index 0000000..1b90e84
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodStateOrUnknown.java
@@ -0,0 +1,7 @@
+// 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.codescanner;
+
+public interface ConcreteMonomorphicMethodStateOrUnknown extends MethodState {}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java
new file mode 100644
index 0000000..1e5ebae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteParameterState.java
@@ -0,0 +1,138 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
+
+public abstract class ConcreteParameterState extends ParameterState {
+
+  enum ConcreteParameterStateKind {
+    ARRAY,
+    CLASS,
+    PRIMITIVE,
+    RECEIVER
+  }
+
+  private Set<MethodParameter> inParameters;
+
+  ConcreteParameterState() {
+    this.inParameters = Collections.emptySet();
+  }
+
+  ConcreteParameterState(MethodParameter inParameter) {
+    this.inParameters = SetUtils.newHashSet(inParameter);
+  }
+
+  public void clearInParameters() {
+    inParameters.clear();
+  }
+
+  public boolean hasInParameters() {
+    return !inParameters.isEmpty();
+  }
+
+  public Set<MethodParameter> getInParameters() {
+    return inParameters;
+  }
+
+  public abstract ConcreteParameterStateKind getKind();
+
+  public boolean isArrayParameter() {
+    return false;
+  }
+
+  public ConcreteArrayTypeParameterState asArrayParameter() {
+    return null;
+  }
+
+  public boolean isClassParameter() {
+    return false;
+  }
+
+  public ConcreteClassTypeParameterState asClassParameter() {
+    return null;
+  }
+
+  public boolean isPrimitiveParameter() {
+    return false;
+  }
+
+  public ConcretePrimitiveTypeParameterState asPrimitiveParameter() {
+    return null;
+  }
+
+  public boolean isReceiverParameter() {
+    return false;
+  }
+
+  public ConcreteReceiverParameterState asReceiverParameter() {
+    return null;
+  }
+
+  @Override
+  public boolean isConcrete() {
+    return true;
+  }
+
+  @Override
+  public ConcreteParameterState asConcrete() {
+    return this;
+  }
+
+  @Override
+  public ParameterState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ParameterState parameterState, Action onChangedAction) {
+    if (parameterState.isUnknown()) {
+      return parameterState;
+    }
+    ConcreteParameterStateKind kind = getKind();
+    ConcreteParameterStateKind otherKind = parameterState.asConcrete().getKind();
+    if (kind == otherKind) {
+      switch (getKind()) {
+        case ARRAY:
+          return asArrayParameter()
+              .mutableJoin(parameterState.asConcrete().asArrayParameter(), onChangedAction);
+        case CLASS:
+          return asClassParameter()
+              .mutableJoin(
+                  appView, parameterState.asConcrete().asClassParameter(), onChangedAction);
+        case PRIMITIVE:
+          return asPrimitiveParameter()
+              .mutableJoin(
+                  appView, parameterState.asConcrete().asPrimitiveParameter(), onChangedAction);
+        case RECEIVER:
+          return asReceiverParameter()
+              .mutableJoin(parameterState.asConcrete().asReceiverParameter(), onChangedAction);
+        default:
+          // Dead.
+      }
+    }
+
+    assert false;
+    return unknown();
+  }
+
+  boolean mutableJoinInParameters(ConcreteParameterState parameterState) {
+    if (parameterState.inParameters.isEmpty()) {
+      return false;
+    }
+    if (inParameters.isEmpty()) {
+      assert inParameters == Collections.<MethodParameter>emptySet();
+      inParameters = Sets.newIdentityHashSet();
+    }
+    return inParameters.addAll(parameterState.inParameters);
+  }
+
+  boolean widenInParameters() {
+    // TODO(b/190154391): Widen to unknown when the size of the collection exceeds a threshold.
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
new file mode 100644
index 0000000..2e5a908
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePolymorphicMethodState.java
@@ -0,0 +1,73 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+public class ConcretePolymorphicMethodState extends ConcreteMethodState {
+
+  private final Map<DynamicType, ConcreteMonomorphicMethodStateOrUnknown> receiverBoundsToState =
+      new HashMap<>();
+
+  public ConcretePolymorphicMethodState(
+      DynamicType receiverBounds, ConcreteMonomorphicMethodStateOrUnknown methodState) {
+    // TODO(b/190154391): Ensure that we use the unknown state instead of mapping unknown -> unknown
+    //  here.
+    receiverBoundsToState.put(receiverBounds, methodState);
+  }
+
+  public void forEach(BiConsumer<DynamicType, MethodState> consumer) {
+    receiverBoundsToState.forEach(consumer);
+  }
+
+  public boolean isEmpty() {
+    return receiverBoundsToState.isEmpty();
+  }
+
+  public MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ConcretePolymorphicMethodState methodState) {
+    assert !isEmpty();
+    assert !methodState.isEmpty();
+    methodState.receiverBoundsToState.forEach(
+        (receiverBounds, stateToAdd) -> {
+          if (stateToAdd.isUnknown()) {
+            receiverBoundsToState.put(receiverBounds, stateToAdd);
+          } else {
+            assert stateToAdd.isMonomorphic();
+            receiverBoundsToState.compute(
+                receiverBounds,
+                (ignore, existingState) -> {
+                  if (existingState == null) {
+                    return stateToAdd;
+                  }
+                  if (existingState.isUnknown()) {
+                    return existingState;
+                  }
+                  assert existingState.isMonomorphic();
+                  return existingState
+                      .asMonomorphic()
+                      .mutableJoin(appView, stateToAdd.asMonomorphic());
+                });
+          }
+        });
+    // TODO(b/190154391): Widen to unknown when the unknown dynamic type is mapped to unknown.
+    return this;
+  }
+
+  @Override
+  public boolean isPolymorphic() {
+    return true;
+  }
+
+  @Override
+  public ConcretePolymorphicMethodState asPolymorphic() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.java
new file mode 100644
index 0000000..0b85fb6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeParameterState.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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public class ConcretePrimitiveTypeParameterState extends ConcreteParameterState {
+
+  private AbstractValue abstractValue;
+
+  public ConcretePrimitiveTypeParameterState(AbstractValue abstractValue) {
+    assert !abstractValue.isUnknown() : "Must use UnknownParameterState";
+    this.abstractValue = abstractValue;
+  }
+
+  public ConcretePrimitiveTypeParameterState(MethodParameter inParameter) {
+    super(inParameter);
+    this.abstractValue = AbstractValue.bottom();
+  }
+
+  public ParameterState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      ConcretePrimitiveTypeParameterState parameterState,
+      Action onChangedAction) {
+    boolean allowNullOrAbstractValue = false;
+    boolean allowNonConstantNumbers = false;
+    AbstractValue oldAbstractValue = abstractValue;
+    abstractValue =
+        abstractValue.join(
+            parameterState.abstractValue,
+            appView.abstractValueFactory(),
+            allowNullOrAbstractValue,
+            allowNonConstantNumbers);
+    if (abstractValue.isUnknown()) {
+      return unknown();
+    }
+    boolean inParametersChanged = mutableJoinInParameters(parameterState);
+    if (widenInParameters()) {
+      return unknown();
+    }
+    if (abstractValue != oldAbstractValue || inParametersChanged) {
+      onChangedAction.execute();
+    }
+    return this;
+  }
+
+  @Override
+  public AbstractValue getAbstractValue() {
+    return abstractValue;
+  }
+
+  @Override
+  public ConcreteParameterStateKind getKind() {
+    return ConcreteParameterStateKind.PRIMITIVE;
+  }
+
+  @Override
+  public boolean isPrimitiveParameter() {
+    return true;
+  }
+
+  @Override
+  public ConcretePrimitiveTypeParameterState asPrimitiveParameter() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.java
new file mode 100644
index 0000000..145d29b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverParameterState.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.optimize.argumentpropagation.codescanner;
+
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.utils.Action;
+
+public class ConcreteReceiverParameterState extends ConcreteParameterState {
+
+  private DynamicType dynamicType;
+
+  public ConcreteReceiverParameterState(DynamicType dynamicType) {
+    this.dynamicType = dynamicType;
+  }
+
+  public ParameterState mutableJoin(
+      ConcreteReceiverParameterState parameterState, Action onChangedAction) {
+    // TODO(b/190154391): Join the dynamic types using SubtypingInfo.
+    // TODO(b/190154391): Take in the static type as an argument, and unset the dynamic type if it
+    //  equals the static type.
+    DynamicType oldDynamicType = dynamicType;
+    dynamicType =
+        dynamicType.equals(parameterState.dynamicType) ? dynamicType : DynamicType.unknown();
+    if (dynamicType.isUnknown()) {
+      return unknown();
+    }
+    boolean inParametersChanged = mutableJoinInParameters(parameterState);
+    if (widenInParameters()) {
+      return unknown();
+    }
+    if (dynamicType != oldDynamicType || inParametersChanged) {
+      onChangedAction.execute();
+    }
+    return this;
+  }
+
+  @Override
+  public AbstractValue getAbstractValue() {
+    return AbstractValue.unknown();
+  }
+
+  public DynamicType getDynamicType() {
+    return dynamicType;
+  }
+
+  @Override
+  public ConcreteParameterStateKind getKind() {
+    return ConcreteParameterStateKind.RECEIVER;
+  }
+
+  @Override
+  public boolean isReceiverParameter() {
+    return true;
+  }
+
+  @Override
+  public ConcreteReceiverParameterState asReceiverParameter() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
new file mode 100644
index 0000000..9f7434b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodParameter.java
@@ -0,0 +1,41 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.Objects;
+
+public class MethodParameter {
+
+  private final DexMethod method;
+  private final int index;
+
+  public MethodParameter(DexMethod method, int index) {
+    this.method = method;
+    this.index = index;
+  }
+
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public int getIndex() {
+    return index;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    MethodParameter methodParameter = (MethodParameter) obj;
+    return method == methodParameter.method && index == methodParameter.index;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(method, index);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
new file mode 100644
index 0000000..5bc6049
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodState.java
@@ -0,0 +1,37 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Supplier;
+
+public interface MethodState {
+
+  static BottomMethodState bottom() {
+    return BottomMethodState.get();
+  }
+
+  static UnknownMethodState unknown() {
+    return UnknownMethodState.get();
+  }
+
+  boolean isBottom();
+
+  boolean isConcrete();
+
+  ConcreteMethodState asConcrete();
+
+  boolean isMonomorphic();
+
+  ConcreteMonomorphicMethodState asMonomorphic();
+
+  boolean isUnknown();
+
+  MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState);
+
+  MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier);
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java
new file mode 100644
index 0000000..936210f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateBase.java
@@ -0,0 +1,46 @@
+// 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.codescanner;
+
+public abstract class MethodStateBase implements MethodState {
+
+  public static BottomMethodState bottom() {
+    return BottomMethodState.get();
+  }
+
+  public static UnknownMethodState unknown() {
+    return UnknownMethodState.get();
+  }
+
+  @Override
+  public boolean isBottom() {
+    return false;
+  }
+
+  @Override
+  public boolean isConcrete() {
+    return false;
+  }
+
+  @Override
+  public ConcreteMethodState asConcrete() {
+    return null;
+  }
+
+  @Override
+  public boolean isMonomorphic() {
+    return false;
+  }
+
+  @Override
+  public ConcreteMonomorphicMethodState asMonomorphic() {
+    return null;
+  }
+
+  @Override
+  public boolean isUnknown() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
new file mode 100644
index 0000000..c8b2456
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
@@ -0,0 +1,94 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+abstract class MethodStateCollection<K> {
+
+  private final Map<K, MethodState> methodStates;
+
+  MethodStateCollection(Map<K, MethodState> methodStates) {
+    this.methodStates = methodStates;
+  }
+
+  abstract K getKey(ProgramMethod method);
+
+  public void addMethodState(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method, MethodState methodState) {
+    addMethodState(appView, getKey(method), methodState);
+  }
+
+  private void addMethodState(
+      AppView<AppInfoWithLiveness> appView, K method, MethodState methodState) {
+    if (methodState.isUnknown()) {
+      methodStates.put(method, methodState);
+    } else {
+      methodStates.compute(
+          method,
+          (ignore, existingMethodState) -> {
+            if (existingMethodState == null) {
+              return methodState;
+            }
+            return existingMethodState.mutableJoin(appView, methodState);
+          });
+    }
+  }
+
+  /**
+   * This intentionally takes a {@link Supplier<MethodState>} to avoid computing the method state
+   * for a given call site when nothing is known about the arguments of the method.
+   */
+  public void addMethodState(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod method,
+      Supplier<MethodState> methodStateSupplier) {
+    addMethodState(appView, getKey(method), methodStateSupplier);
+  }
+
+  public void addMethodState(
+      AppView<AppInfoWithLiveness> appView, K method, Supplier<MethodState> methodStateSupplier) {
+    methodStates.compute(
+        method,
+        (ignore, existingMethodState) -> {
+          if (existingMethodState == null) {
+            return methodStateSupplier.get();
+          }
+          return existingMethodState.mutableJoin(appView, methodStateSupplier);
+        });
+  }
+
+  public void addMethodStates(
+      AppView<AppInfoWithLiveness> appView, MethodStateCollection<K> other) {
+    other.methodStates.forEach(
+        (method, methodState) -> addMethodState(appView, method, methodState));
+  }
+
+  public void forEach(BiConsumer<K, MethodState> consumer) {
+    methodStates.forEach(consumer);
+  }
+
+  public MethodState get(ProgramMethod method) {
+    return methodStates.getOrDefault(getKey(method), MethodState.bottom());
+  }
+
+  public boolean isEmpty() {
+    return methodStates.isEmpty();
+  }
+
+  public MethodState remove(ProgramMethod method) {
+    MethodState removed = methodStates.remove(getKey(method));
+    return removed != null ? removed : MethodState.bottom();
+  }
+
+  public void set(ProgramMethod method, MethodState methodState) {
+    methodStates.put(getKey(method), methodState);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java
new file mode 100644
index 0000000..ea48bfb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionByReference.java
@@ -0,0 +1,31 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MethodStateCollectionByReference extends MethodStateCollection<DexMethod> {
+
+  private MethodStateCollectionByReference(Map<DexMethod, MethodState> methodStates) {
+    super(methodStates);
+  }
+
+  public static MethodStateCollectionByReference create() {
+    return new MethodStateCollectionByReference(new IdentityHashMap<>());
+  }
+
+  public static MethodStateCollectionByReference createConcurrent() {
+    return new MethodStateCollectionByReference(new ConcurrentHashMap<>());
+  }
+
+  @Override
+  DexMethod getKey(ProgramMethod method) {
+    return method.getReference();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java
new file mode 100644
index 0000000..2113be0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollectionBySignature.java
@@ -0,0 +1,31 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MethodStateCollectionBySignature extends MethodStateCollection<DexMethodSignature> {
+
+  private MethodStateCollectionBySignature(Map<DexMethodSignature, MethodState> methodStates) {
+    super(methodStates);
+  }
+
+  public static MethodStateCollectionBySignature create() {
+    return new MethodStateCollectionBySignature(new HashMap<>());
+  }
+
+  public static MethodStateCollectionBySignature createConcurrent() {
+    return new MethodStateCollectionBySignature(new ConcurrentHashMap<>());
+  }
+
+  @Override
+  DexMethodSignature getKey(ProgramMethod method) {
+    return method.getMethodSignature();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
new file mode 100644
index 0000000..2c9a205
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ParameterState.java
@@ -0,0 +1,39 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public abstract class ParameterState {
+
+  public static UnknownParameterState unknown() {
+    return UnknownParameterState.get();
+  }
+
+  public abstract AbstractValue getAbstractValue();
+
+  public boolean isConcrete() {
+    return false;
+  }
+
+  public ConcreteParameterState asConcrete() {
+    return null;
+  }
+
+  public boolean isUnknown() {
+    return false;
+  }
+
+  public final ParameterState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ParameterState parameterState) {
+    return mutableJoin(appView, parameterState, Action.empty());
+  }
+
+  public abstract ParameterState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ParameterState parameterState, Action onChangedAction);
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
new file mode 100644
index 0000000..1cfe728
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownMethodState.java
@@ -0,0 +1,38 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Supplier;
+
+// Use this when the nothing is known.
+public class UnknownMethodState extends MethodStateBase
+    implements ConcreteMonomorphicMethodStateOrUnknown {
+
+  private static final UnknownMethodState INSTANCE = new UnknownMethodState();
+
+  private UnknownMethodState() {}
+
+  public static UnknownMethodState get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isUnknown() {
+    return true;
+  }
+
+  @Override
+  public MethodState mutableJoin(AppView<AppInfoWithLiveness> appView, MethodState methodState) {
+    return this;
+  }
+
+  @Override
+  public MethodState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, Supplier<MethodState> methodStateSupplier) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
new file mode 100644
index 0000000..bfc835d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownParameterState.java
@@ -0,0 +1,37 @@
+// 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.codescanner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+
+public class UnknownParameterState extends ParameterState {
+
+  private static final UnknownParameterState INSTANCE = new UnknownParameterState();
+
+  private UnknownParameterState() {}
+
+  public static UnknownParameterState get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public AbstractValue getAbstractValue() {
+    return AbstractValue.unknown();
+  }
+
+  @Override
+  public boolean isUnknown() {
+    return true;
+  }
+
+  @Override
+  public ParameterState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ParameterState parameterState, Action onChangedAction) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
new file mode 100644
index 0000000..cba390e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
@@ -0,0 +1,302 @@
+// 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.propagation;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+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.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+
+public class InParameterFlowPropagator {
+
+  final AppView<AppInfoWithLiveness> appView;
+  final MethodStateCollectionByReference methodStates;
+
+  public InParameterFlowPropagator(
+      AppView<AppInfoWithLiveness> appView, MethodStateCollectionByReference methodStates) {
+    this.appView = appView;
+    this.methodStates = methodStates;
+  }
+
+  public void run(ExecutorService executorService) throws ExecutionException {
+    // Build a graph with an edge from parameter p -> parameter p' if all argument information for p
+    // must be included in the argument information for p'.
+    FlowGraph flowGraph = new FlowGraph(appView.appInfo().classes());
+
+    // Build a worklist containing all the parameter nodes.
+    Deque<ParameterNode> worklist = new ArrayDeque<>();
+    flowGraph.forEachNode(worklist::add);
+
+    // Repeatedly propagate argument information through edges in the flow graph until there are no
+    // more changes.
+    // TODO(b/190154391): Consider parallelizing the flow propagation. There are a few scenarios
+    //  that need to be covered, such as (i) two threads could race to update the same parameter
+    //  state, (ii) a thread may try to propagate a parameter state to its successors while
+    //  another thread is trying to update the state of the parameter itself.
+    // TODO(b/190154391): Consider a path p1 -> p2 -> p3 in the graph. If we process p2 first, then
+    //  p3, and then p1, then the processing of p1 could cause p2 to change, which means that we
+    //  need to reprocess p2 and then p3. If we always process leaves in the graph first, we would
+    //  process p1, then p2, then p3, and then be done.
+    // TODO(b/190154391): Prune the graph on-the-fly. If the argument information for a parameter
+    //  becomes unknown, we could consider clearing its predecessors since none of the predecessors
+    //  could contribute any information even if they change.
+    while (!worklist.isEmpty()) {
+      ParameterNode parameterNode = worklist.removeLast();
+      parameterNode.unsetPending();
+      propagate(
+          parameterNode,
+          affectedNode -> {
+            // No need to enqueue the affected node if it is already in the worklist or if it does
+            // not have any successors (i.e., the successor is a leaf).
+            if (!affectedNode.isPending() && affectedNode.hasSuccessors()) {
+              worklist.add(affectedNode);
+              affectedNode.setPending();
+            }
+          });
+    }
+
+    // The algorithm only changes the parameter states of each monomorphic method state. In case any
+    // of these method states have effectively become unknown, we replace them by the canonicalized
+    // unknown method state.
+    postProcessMethodStates(executorService);
+  }
+
+  private void propagate(
+      ParameterNode parameterNode, Consumer<ParameterNode> affectedNodeConsumer) {
+    ParameterState parameterState = parameterNode.getState();
+    for (ParameterNode successorNode : parameterNode.getSuccessors()) {
+      successorNode.addState(
+          appView, parameterState, () -> affectedNodeConsumer.accept(successorNode));
+    }
+  }
+
+  private void postProcessMethodStates(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        appView.appInfo().classes(), this::postProcessMethodStates, executorService);
+  }
+
+  private void postProcessMethodStates(DexProgramClass clazz) {
+    clazz.forEachProgramMethod(this::postProcessMethodState);
+  }
+
+  private void postProcessMethodState(ProgramMethod method) {
+    ConcreteMethodState methodState = methodStates.get(method).asConcrete();
+    if (methodState == null) {
+      return;
+    }
+    assert methodState.isMonomorphic();
+    for (ParameterState parameterState : methodState.asMonomorphic().getParameterStates()) {
+      if (!parameterState.isUnknown()) {
+        return;
+      }
+    }
+    methodStates.set(method, MethodState.unknown());
+  }
+
+  private class FlowGraph {
+
+    private final Map<DexMethod, Int2ReferenceMap<ParameterNode>> nodes = new IdentityHashMap<>();
+
+    public FlowGraph(Iterable<DexProgramClass> classes) {
+      classes.forEach(this::add);
+    }
+
+    void forEachNode(Consumer<? super ParameterNode> consumer) {
+      nodes.values().forEach(nodesForMethod -> nodesForMethod.values().forEach(consumer));
+    }
+
+    private void add(DexProgramClass clazz) {
+      clazz.forEachProgramMethod(this::add);
+    }
+
+    private void add(ProgramMethod method) {
+      MethodState methodState = methodStates.get(method);
+
+      // No need to create nodes for parameters with no in-flow or no useful information.
+      if (methodState.isBottom() || methodState.isUnknown()) {
+        return;
+      }
+
+      // Add nodes for the parameters for which we have non-trivial information.
+      ConcreteMonomorphicMethodState monomorphicMethodState =
+          methodState.asConcrete().asMonomorphic();
+      List<ParameterState> parameterStates = monomorphicMethodState.getParameterStates();
+      for (int parameterIndex = 0; parameterIndex < parameterStates.size(); parameterIndex++) {
+        ParameterState parameterState = parameterStates.get(parameterIndex);
+        add(method, parameterIndex, monomorphicMethodState, parameterState);
+      }
+    }
+
+    private void add(
+        ProgramMethod method,
+        int parameterIndex,
+        ConcreteMonomorphicMethodState methodState,
+        ParameterState parameterState) {
+      // No need to create nodes for parameters we don't know anything about.
+      if (parameterState.isUnknown()) {
+        return;
+      }
+
+      ConcreteParameterState concreteParameterState = parameterState.asConcrete();
+
+      // No need to create a node for a parameter that doesn't depend on any other parameters
+      // (unless some other parameter depends on this parameter).
+      if (!concreteParameterState.hasInParameters()) {
+        return;
+      }
+
+      ParameterNode node =
+          getOrCreateParameterNode(method.getReference(), parameterIndex, methodState);
+      for (MethodParameter inParameter : concreteParameterState.getInParameters()) {
+        MethodState enclosingMethodState = getEnclosingMethodStateForParameter(inParameter);
+        if (enclosingMethodState.isBottom()) {
+          // The current method is called from a dead method; no need to propagate any information
+          // from the dead call site.
+          continue;
+        }
+
+        if (enclosingMethodState.isUnknown()) {
+          // The parameter depends on another parameter for which we don't know anything.
+          node.clearPredecessors();
+          node.setState(ParameterState.unknown());
+          break;
+        }
+
+        assert enclosingMethodState.isConcrete();
+        assert enclosingMethodState.asConcrete().isMonomorphic();
+
+        ParameterNode predecessor =
+            getOrCreateParameterNode(
+                inParameter.getMethod(),
+                inParameter.getIndex(),
+                enclosingMethodState.asConcrete().asMonomorphic());
+        node.addPredecessor(predecessor);
+      }
+      concreteParameterState.clearInParameters();
+    }
+
+    private ParameterNode getOrCreateParameterNode(
+        DexMethod key, int parameterIndex, ConcreteMonomorphicMethodState methodState) {
+      Int2ReferenceMap<ParameterNode> parameterNodesForMethod =
+          nodes.computeIfAbsent(key, ignoreKey(Int2ReferenceOpenHashMap::new));
+      return parameterNodesForMethod.compute(
+          parameterIndex,
+          (ignore, parameterNode) ->
+              parameterNode != null
+                  ? parameterNode
+                  : new ParameterNode(methodState, parameterIndex));
+    }
+
+    private MethodState getEnclosingMethodStateForParameter(MethodParameter methodParameter) {
+      DexMethod methodReference = methodParameter.getMethod();
+      ProgramMethod method =
+          methodReference.lookupOnProgramClass(
+              asProgramClassOrNull(
+                  appView.definitionFor(methodParameter.getMethod().getHolderType())));
+      if (method == null) {
+        // Conservatively return unknown if for some reason we can't find the method.
+        assert false;
+        return MethodState.unknown();
+      }
+      return methodStates.get(method);
+    }
+  }
+
+  static class ParameterNode {
+
+    private final ConcreteMonomorphicMethodState methodState;
+    private final int parameterIndex;
+
+    private final Set<ParameterNode> predecessors = Sets.newIdentityHashSet();
+    private final Set<ParameterNode> successors = Sets.newIdentityHashSet();
+
+    private boolean pending = true;
+
+    ParameterNode(ConcreteMonomorphicMethodState methodState, int parameterIndex) {
+      this.methodState = methodState;
+      this.parameterIndex = parameterIndex;
+    }
+
+    void addPredecessor(ParameterNode predecessor) {
+      predecessor.successors.add(this);
+      predecessors.add(predecessor);
+    }
+
+    void clearPredecessors() {
+      for (ParameterNode predecessor : predecessors) {
+        predecessor.successors.remove(this);
+      }
+      predecessors.clear();
+    }
+
+    ParameterState getState() {
+      return methodState.getParameterState(parameterIndex);
+    }
+
+    Set<ParameterNode> getSuccessors() {
+      return successors;
+    }
+
+    boolean hasSuccessors() {
+      return !successors.isEmpty();
+    }
+
+    boolean isPending() {
+      return pending;
+    }
+
+    void addState(
+        AppView<AppInfoWithLiveness> appView,
+        ParameterState parameterStateToAdd,
+        Action onChangedAction) {
+      ParameterState oldParameterState = getState();
+      ParameterState newParameterState =
+          oldParameterState.mutableJoin(appView, parameterStateToAdd, onChangedAction);
+      if (newParameterState != oldParameterState) {
+        setState(newParameterState);
+        onChangedAction.execute();
+      }
+    }
+
+    void setPending() {
+      assert !isPending();
+      pending = true;
+    }
+
+    void setState(ParameterState parameterState) {
+      methodState.setParameterState(parameterIndex, parameterState);
+    }
+
+    void unsetPending() {
+      assert pending;
+      pending = false;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
new file mode 100644
index 0000000..99a0bac
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -0,0 +1,161 @@
+// 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.propagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionBySignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Propagates the argument information for each interface method I.m() to the possible dispatch
+ * targets that are declared on (non-interface) classes that are not a subtype of I.
+ *
+ * <p>Example: The argument information stored for I.m() is propagated to the method A.m().
+ *
+ * <pre>
+ *   interface I { void m(int x); }
+ *   class A { public void m(int x) { ... } }
+ *   class B extends A implements I {}
+ * </pre>
+ */
+public class InterfaceMethodArgumentPropagator extends MethodArgumentPropagator {
+
+  // Contains the argument information for each interface method (including inherited interface
+  // methods) on the seen but not finished interfaces.
+  final Map<DexProgramClass, MethodStateCollectionBySignature> methodStatesToPropagate =
+      new IdentityHashMap<>();
+
+  public InterfaceMethodArgumentPropagator(
+      AppView<AppInfoWithLiveness> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      MethodStateCollectionByReference methodStates) {
+    super(appView, immediateSubtypingInfo, methodStates);
+  }
+
+  @Override
+  public void run(Collection<DexProgramClass> stronglyConnectedComponent) {
+    super.run(stronglyConnectedComponent);
+    assert verifyAllInterfacesFinished(stronglyConnectedComponent);
+  }
+
+  @Override
+  public void forEachSubClass(DexProgramClass clazz, Consumer<DexProgramClass> consumer) {
+    for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(clazz)) {
+      if (subclass.isInterface()) {
+        consumer.accept(subclass);
+      }
+    }
+  }
+
+  @Override
+  public boolean isRoot(DexProgramClass clazz) {
+    return clazz.isInterface() && super.isRoot(clazz);
+  }
+
+  @Override
+  public void visit(DexProgramClass clazz) {
+    assert !methodStatesToPropagate.containsKey(clazz);
+    MethodStateCollectionBySignature interfaceState = computeInterfaceState(clazz);
+    propagateInterfaceStateToClassHierarchy(clazz, interfaceState);
+  }
+
+  @Override
+  public void prune(DexProgramClass clazz) {
+    methodStatesToPropagate.remove(clazz);
+  }
+
+  private MethodStateCollectionBySignature computeInterfaceState(
+      DexProgramClass interfaceDefinition) {
+    // Join the state for all parent interfaces into a fresh state created for this interface.
+    MethodStateCollectionBySignature interfaceState = MethodStateCollectionBySignature.create();
+    immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+        interfaceDefinition,
+        (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
+        (supertype, superclass) -> {
+          MethodStateCollectionBySignature implementedInterfaceState =
+              methodStatesToPropagate.get(superclass.asProgramClass());
+          assert implementedInterfaceState != null;
+          interfaceState.addMethodStates(appView, implementedInterfaceState);
+        });
+
+    // Add any argument information for virtual methods on the current interface to the state.
+    interfaceDefinition.forEachProgramVirtualMethod(
+        method -> {
+          MethodState methodState = methodStates.get(method);
+          if (methodState.isBottom()) {
+            return;
+          }
+
+          // TODO(b/190154391): We should always have an unknown or polymorphic state, but it would
+          //  be better to use a monomorphic state when the interface method is a default method
+          //  with no overrides (CF backend only). In this case, there is no need to add methodState
+          //  to interfaceState.
+          assert methodState.isUnknown() || methodState.asConcrete().isPolymorphic();
+          interfaceState.addMethodState(appView, method, methodState);
+        });
+
+    methodStatesToPropagate.put(interfaceDefinition, interfaceState);
+    return interfaceState;
+  }
+
+  private void propagateInterfaceStateToClassHierarchy(
+      DexProgramClass interfaceDefinition, MethodStateCollectionBySignature interfaceState) {
+    // Propagate the argument information for the interface's non-private virtual methods to the
+    // the possible dispatch targets declared on classes that are not a subtype of the interface.
+    immediateSubtypingInfo.forEachImmediateSubClassMatching(
+        interfaceDefinition,
+        subclass -> !subclass.isInterface(),
+        subclass ->
+            interfaceState.forEach(
+                (interfaceMethod, interfaceMethodState) -> {
+                  // TODO(b/190154391): Change resolution to take a signature.
+                  DexMethod interfaceMethodToResolve =
+                      appView
+                          .dexItemFactory()
+                          .createMethod(
+                              subclass.getType(),
+                              interfaceMethod.getProto(),
+                              interfaceMethod.getName());
+                  SingleResolutionResult resolutionResult =
+                      appView
+                          .appInfo()
+                          .resolveMethodOnClass(interfaceMethodToResolve, subclass)
+                          .asSingleResolution();
+                  if (resolutionResult == null) {
+                    assert false;
+                    return;
+                  }
+
+                  ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
+                  if (resolvedMethod == null
+                      || resolvedMethod.getHolder().isInterface()
+                      || resolvedMethod.getHolder() == subclass) {
+                    return;
+                  }
+
+                  methodStates.addMethodState(appView, resolvedMethod, interfaceMethodState);
+                }));
+  }
+
+  private boolean verifyAllInterfacesFinished(
+      Collection<DexProgramClass> stronglyConnectedComponent) {
+    assert stronglyConnectedComponent.stream()
+        .filter(DexClass::isInterface)
+        .allMatch(this::isClassFinished);
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/MethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/MethodArgumentPropagator.java
new file mode 100644
index 0000000..4e6723f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/MethodArgumentPropagator.java
@@ -0,0 +1,24 @@
+// 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.propagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public abstract class MethodArgumentPropagator extends DepthFirstTopDownClassHierarchyTraversal {
+
+  final MethodStateCollectionByReference methodStates;
+
+  public MethodArgumentPropagator(
+      AppView<AppInfoWithLiveness> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      MethodStateCollectionByReference methodStates) {
+    super(appView, immediateSubtypingInfo);
+    this.methodStates = methodStates;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
new file mode 100644
index 0000000..14c58f8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -0,0 +1,254 @@
+// 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.propagation;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePolymorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionBySignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class VirtualDispatchMethodArgumentPropagator extends MethodArgumentPropagator {
+
+  class PropagationState {
+
+    // Argument information for virtual methods that must be propagated to all overrides (i.e., this
+    // information does not have a lower bound).
+    final MethodStateCollectionBySignature active = MethodStateCollectionBySignature.create();
+
+    // Argument information for virtual methods that must be propagated to all overrides that are
+    // above the given lower bound.
+    final Map<DexType, MethodStateCollectionBySignature> activeUntilLowerBound =
+        new IdentityHashMap<>();
+
+    // Argument information for virtual methods that is currently inactive, but should be propagated
+    // to all overrides below a given upper bound.
+    final Map<DynamicType, MethodStateCollectionBySignature> inactiveUntilUpperBound =
+        new HashMap<>();
+
+    PropagationState(DexProgramClass clazz) {
+      // Join the argument information from each of the super types.
+      immediateSubtypingInfo.forEachImmediateSuperClassMatching(
+          clazz,
+          (supertype, superclass) -> superclass != null && superclass.isProgramClass(),
+          (supertype, superclass) -> addParentState(clazz, superclass.asProgramClass()));
+    }
+
+    // TODO(b/190154391): This currently copies the state of the superclass into its immediate
+    //  given subclass. Instead of copying the state, consider linking the states. This would reduce
+    //  memory usage, but would require visiting all transitive (program) super classes for each
+    //  subclass.
+    private void addParentState(DexProgramClass clazz, DexProgramClass superclass) {
+      ClassTypeElement classType =
+          TypeElement.fromDexType(clazz.getType(), maybeNull(), appView).asClassType();
+
+      PropagationState parentState = propagationStates.get(superclass.asProgramClass());
+      assert parentState != null;
+
+      // Add the argument information that must be propagated to all method overrides.
+      active.addMethodStates(appView, parentState.active);
+
+      // Add the argument information that is active until a given lower bound.
+      parentState.activeUntilLowerBound.forEach(
+          (lowerBound, activeMethodState) -> {
+            if (lowerBound != superclass.getType()) {
+              // TODO(b/190154391): Verify that the lower bound is a subtype of the current.
+              //  Otherwise we carry this information to all subtypes although there is no need to.
+              activeUntilLowerBound
+                  .computeIfAbsent(lowerBound, ignoreKey(MethodStateCollectionBySignature::create))
+                  .addMethodStates(appView, activeMethodState);
+            } else {
+              // No longer active.
+            }
+          });
+
+      // Add the argument information that is inactive until a given upper bound.
+      parentState.inactiveUntilUpperBound.forEach(
+          (bounds, inactiveMethodState) -> {
+            ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
+            if (upperBound.equalUpToNullability(classType)) {
+              // The upper bound is the current class, thus this inactive information now becomes
+              // active.
+              if (bounds.hasDynamicLowerBoundType()) {
+                activeUntilLowerBound
+                    .computeIfAbsent(
+                        bounds.getDynamicLowerBoundType().getClassType(),
+                        ignoreKey(MethodStateCollectionBySignature::create))
+                    .addMethodStates(appView, inactiveMethodState);
+              } else {
+                active.addMethodStates(appView, inactiveMethodState);
+              }
+            } else {
+              // Still inactive.
+              // TODO(b/190154391): Only carry this information downwards if the upper bound is a
+              //  subtype of this class. Otherwise we carry this information to all subtypes,
+              //  although clearly the information will never become active.
+              inactiveUntilUpperBound
+                  .computeIfAbsent(bounds, ignoreKey(MethodStateCollectionBySignature::create))
+                  .addMethodStates(appView, inactiveMethodState);
+            }
+          });
+    }
+
+    private MethodState computeMethodStateForPolymorhicMethod(ProgramMethod method) {
+      assert method.getDefinition().isNonPrivateVirtualMethod();
+      MethodState methodState = active.get(method);
+      for (MethodStateCollectionBySignature methodStates : activeUntilLowerBound.values()) {
+        methodState = methodState.mutableJoin(appView, methodStates.get(method));
+      }
+      return methodState;
+    }
+  }
+
+  // For each class, stores the argument information for each virtual method on this class and all
+  // direct and indirect super classes.
+  //
+  // This data structure is populated during a top-down traversal over the class hierarchy, such
+  // that entries in the map can be removed when the top-down traversal has visited all subtypes of
+  // a given node.
+  final Map<DexProgramClass, PropagationState> propagationStates = new IdentityHashMap<>();
+
+  public VirtualDispatchMethodArgumentPropagator(
+      AppView<AppInfoWithLiveness> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      MethodStateCollectionByReference methodStates) {
+    super(appView, immediateSubtypingInfo, methodStates);
+  }
+
+  @Override
+  public void run(Collection<DexProgramClass> stronglyConnectedComponent) {
+    super.run(stronglyConnectedComponent);
+    assert verifyAllClassesFinished(stronglyConnectedComponent);
+    assert verifyStatePruned();
+  }
+
+  @Override
+  public void visit(DexProgramClass clazz) {
+    assert !propagationStates.containsKey(clazz);
+    PropagationState propagationState = computePropagationState(clazz);
+    computeFinalMethodStates(clazz, propagationState);
+  }
+
+  private PropagationState computePropagationState(DexProgramClass clazz) {
+    ClassTypeElement classType =
+        TypeElement.fromDexType(clazz.getType(), maybeNull(), appView).asClassType();
+    PropagationState propagationState = new PropagationState(clazz);
+
+    // Join the argument information from the methods of the current class.
+    clazz.forEachProgramVirtualMethod(
+        method -> {
+          MethodState methodState = methodStates.get(method);
+          if (methodState.isBottom()) {
+            return;
+          }
+
+          // TODO(b/190154391): Add an unknown polymorphic method state, such that we can
+          //  distinguish monomorphic unknown method states from polymorphic unknown method states.
+          //  We only need to propagate polymorphic unknown method states here.
+          if (methodState.isUnknown()) {
+            propagationState.active.addMethodState(appView, method, methodState);
+            return;
+          }
+
+          ConcreteMethodState concreteMethodState = methodState.asConcrete();
+          if (concreteMethodState.isMonomorphic()) {
+            // No need to propagate information for methods that do not override other methods and
+            // are not themselves overridden.
+            return;
+          }
+
+          ConcretePolymorphicMethodState polymorphicMethodState =
+              concreteMethodState.asPolymorphic();
+          polymorphicMethodState.forEach(
+              (bounds, methodStateForBounds) -> {
+                if (bounds.isUnknown()) {
+                  propagationState.active.addMethodState(appView, method, methodStateForBounds);
+                } else {
+                  // TODO(b/190154391): Verify that the bounds are not trivial according to the
+                  //  static receiver type.
+                  ClassTypeElement upperBound = bounds.getDynamicUpperBoundType().asClassType();
+                  if (upperBound.equalUpToNullability(classType)) {
+                    if (bounds.hasDynamicLowerBoundType()) {
+                      // TODO(b/190154391): Verify that the lower bound is a subtype of the current
+                      //  class.
+                      propagationState
+                          .activeUntilLowerBound
+                          .computeIfAbsent(
+                              bounds.getDynamicLowerBoundType().getClassType(),
+                              ignoreKey(MethodStateCollectionBySignature::create))
+                          .addMethodState(appView, method, methodStateForBounds);
+                    } else {
+                      propagationState.active.addMethodState(appView, method, methodStateForBounds);
+                    }
+                  } else {
+                    assert !classType.lessThanOrEqualUpToNullability(upperBound, appView);
+                    propagationState
+                        .inactiveUntilUpperBound
+                        .computeIfAbsent(
+                            bounds, ignoreKey(MethodStateCollectionBySignature::create))
+                        .addMethodState(appView, method, methodStateForBounds);
+                  }
+                }
+              });
+        });
+
+    propagationStates.put(clazz, propagationState);
+    return propagationState;
+  }
+
+  private void computeFinalMethodStates(DexProgramClass clazz, PropagationState propagationState) {
+    clazz.forEachProgramMethod(method -> computeFinalMethodState(method, propagationState));
+  }
+
+  private void computeFinalMethodState(ProgramMethod method, PropagationState propagationState) {
+    if (!method.getDefinition().hasCode()) {
+      methodStates.remove(method);
+      return;
+    }
+
+    MethodState methodState = methodStates.get(method);
+
+    // If this is a polymorphic method, we need to compute the method state to account for dynamic
+    // dispatch.
+    if (methodState.isConcrete() && methodState.asConcrete().isPolymorphic()) {
+      methodState = propagationState.computeMethodStateForPolymorhicMethod(method);
+      assert !methodState.isConcrete() || methodState.asConcrete().isMonomorphic();
+      methodStates.set(method, methodState);
+    }
+  }
+
+  @Override
+  public void prune(DexProgramClass clazz) {
+    propagationStates.remove(clazz);
+  }
+
+  private boolean verifyAllClassesFinished(Collection<DexProgramClass> stronglyConnectedComponent) {
+    for (DexProgramClass clazz : stronglyConnectedComponent) {
+      assert isClassFinished(clazz);
+    }
+    return true;
+  }
+
+  private boolean verifyStatePruned() {
+    assert propagationStates.isEmpty();
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
new file mode 100644
index 0000000..379efaf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/DepthFirstTopDownClassHierarchyTraversal.java
@@ -0,0 +1,190 @@
+// 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.utils;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public abstract class DepthFirstTopDownClassHierarchyTraversal {
+
+  // The state of a given class in the top-down traversal.
+  private enum TraversalState {
+    // Represents that a given class and all of its direct and indirect supertypes have been
+    // visited by the top-down traversal, but all of the direct and indirect subtypes are still
+    // not visited.
+    SEEN,
+    // Represents that a given class and all of its direct and indirect subtypes have been visited.
+    // Such nodes will never be seen again in the top-down traversal, and any state stored for
+    // such nodes can be pruned.
+    FINISHED
+  }
+
+  protected final AppView<AppInfoWithLiveness> appView;
+  protected final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+
+  // Contains the traversal state for each class. If a given class is not in the map the class is
+  // not yet seen.
+  private final Map<DexProgramClass, TraversalState> states = new IdentityHashMap<>();
+
+  // The class hierarchy is not a tree, thus for completeness we need to process all parent
+  // interfaces for a given class or interface before continuing the top-down traversal. When the
+  // top-down traversal for a given root returns, this means that there may be interfaces that are
+  // seen but not finished. These interfaces are added to this collection such that we can
+  // prioritize them over classes or interfaces that are yet not seen. This leads to more efficient
+  // state pruning, since the state for these interfaces can be pruned when they transition to being
+  // finished.
+  //
+  // See also prioritizeNewlySeenButNotFinishedRoots().
+  private final List<DexProgramClass> newlySeenButNotFinishedRoots = new ArrayList<>();
+
+  public DepthFirstTopDownClassHierarchyTraversal(
+      AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+    this.appView = appView;
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
+  }
+
+  public abstract void visit(DexProgramClass clazz);
+
+  public abstract void prune(DexProgramClass clazz);
+
+  public void run(Collection<DexProgramClass> stronglyConnectedComponent) {
+    // Perform a top-down traversal from each root in the strongly connected component.
+    Deque<DexProgramClass> roots = computeRoots(stronglyConnectedComponent);
+    while (!roots.isEmpty()) {
+      DexProgramClass root = roots.removeLast();
+      traverse(root);
+      prioritizeNewlySeenButNotFinishedRoots(roots);
+    }
+  }
+
+  private Deque<DexProgramClass> computeRoots(
+      Collection<DexProgramClass> stronglyConnectedComponent) {
+    Deque<DexProgramClass> roots = new ArrayDeque<>();
+    for (DexProgramClass clazz : stronglyConnectedComponent) {
+      if (isRoot(clazz)) {
+        roots.add(clazz);
+      }
+    }
+    return roots;
+  }
+
+  public boolean isRoot(DexProgramClass clazz) {
+    DexProgramClass superclass = asProgramClassOrNull(appView.definitionFor(clazz.getSuperType()));
+    if (superclass != null) {
+      return false;
+    }
+    for (DexType implementedType : clazz.getInterfaces()) {
+      DexProgramClass implementedClass =
+          asProgramClassOrNull(appView.definitionFor(implementedType));
+      if (implementedClass != null) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private void prioritizeNewlySeenButNotFinishedRoots(Deque<DexProgramClass> roots) {
+    assert newlySeenButNotFinishedRoots.stream()
+        .allMatch(
+            newlySeenButNotFinishedRoot -> {
+              assert newlySeenButNotFinishedRoot.isInterface();
+              assert isRoot(newlySeenButNotFinishedRoot);
+              assert isClassSeenButNotFinished(newlySeenButNotFinishedRoot);
+              return true;
+            });
+    // Prioritize this interface over other not yet seen interfaces. This leads to more efficient
+    // state pruning.
+    roots.addAll(newlySeenButNotFinishedRoots);
+    newlySeenButNotFinishedRoots.clear();
+  }
+
+  private void traverse(DexProgramClass clazz) {
+    // Check it the class and all of its subtypes are already processed.
+    if (isClassFinished(clazz)) {
+      return;
+    }
+
+    // Before continuing the top-down traversal, ensure that all super interfaces are processed,
+    // but without visiting the entire subtree of each super interface.
+    if (!isClassSeenButNotFinished(clazz)) {
+      processImplementedInterfaces(clazz);
+      processClass(clazz);
+    }
+
+    processSubclasses(clazz);
+    markFinished(clazz);
+  }
+
+  private void processImplementedInterfaces(DexProgramClass interfaceDefinition) {
+    assert !isClassSeenButNotFinished(interfaceDefinition);
+    assert !isClassFinished(interfaceDefinition);
+    for (DexType implementedType : interfaceDefinition.getInterfaces()) {
+      DexProgramClass implementedDefinition =
+          asProgramClassOrNull(appView.definitionFor(implementedType));
+      if (implementedDefinition == null || isClassSeenButNotFinished(implementedDefinition)) {
+        continue;
+      }
+      assert isClassUnseen(implementedDefinition);
+      processImplementedInterfaces(implementedDefinition);
+      processClass(implementedDefinition);
+
+      // If this is a root, then record that this root is seen but not finished.
+      if (isRoot(implementedDefinition)) {
+        newlySeenButNotFinishedRoots.add(implementedDefinition);
+      }
+    }
+  }
+
+  private void processSubclasses(DexProgramClass clazz) {
+    forEachSubClass(clazz, this::traverse);
+  }
+
+  public void forEachSubClass(DexProgramClass clazz, Consumer<DexProgramClass> consumer) {
+    immediateSubtypingInfo.getSubclasses(clazz).forEach(consumer);
+  }
+
+  private void processClass(DexProgramClass interfaceDefinition) {
+    assert !isClassSeenButNotFinished(interfaceDefinition);
+    assert !isClassFinished(interfaceDefinition);
+    visit(interfaceDefinition);
+    markSeenButNotFinished(interfaceDefinition);
+  }
+
+  protected boolean isClassUnseen(DexProgramClass clazz) {
+    return !states.containsKey(clazz);
+  }
+
+  protected boolean isClassSeenButNotFinished(DexProgramClass clazz) {
+    return states.get(clazz) == TraversalState.SEEN;
+  }
+
+  protected boolean isClassFinished(DexProgramClass clazz) {
+    return states.get(clazz) == TraversalState.FINISHED;
+  }
+
+  private void markSeenButNotFinished(DexProgramClass clazz) {
+    assert isClassUnseen(clazz);
+    states.put(clazz, TraversalState.SEEN);
+  }
+
+  private void markFinished(DexProgramClass clazz) {
+    assert isClassSeenButNotFinished(clazz);
+    states.put(clazz, TraversalState.FINISHED);
+    prune(clazz);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index 69c59b5..5550d52 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -22,8 +23,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
@@ -34,6 +35,7 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -120,7 +122,7 @@
 
   private void processClass(DexProgramClass clazz, SubtypingInfo subtypingInfo) {
     Set<DexType> subtypes = subtypingInfo.allImmediateSubtypes(clazz.type);
-    Set<DexProgramClass> subclasses = new TreeSet<>((x, y) -> x.type.compareTo(y.type));
+    Set<DexProgramClass> subclasses = new TreeSet<>(Comparator.comparing(DexClass::getType));
     for (DexType subtype : subtypes) {
       DexProgramClass subclass = asProgramClassOrNull(appView.definitionFor(subtype));
       if (subclass == null) {
@@ -225,7 +227,7 @@
         appView.dexItemFactory().createMethod(clazz.type, invokedMethod.proto, invokedMethod.name);
 
     // The targeted method must be present on the new holder class for this to be feasible.
-    ResolutionResult resolutionResult =
+    MethodResolutionResult resolutionResult =
         appView.appInfo().resolveMethodOnClass(methodToInvoke, clazz);
     if (!resolutionResult.isSingleResolution()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index e193f4c..6621857 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.BiMap;
@@ -319,12 +320,14 @@
 
     private final AppView<?> appView;
     private final DexItemFactory dexItemFactory;
+    private final InternalOptions options;
     private final ProguardConfiguration proguardConfiguration;
     private final MinificationPackageNamingStrategy packageMinificationStrategy;
 
     public DefaultRepackagingConfiguration(AppView<?> appView) {
       this.appView = appView;
       this.dexItemFactory = appView.dexItemFactory();
+      this.options = appView.options();
       this.proguardConfiguration = appView.options().getProguardConfiguration();
       this.packageMinificationStrategy = new MinificationPackageNamingStrategy(appView);
     }
@@ -383,12 +386,12 @@
       // item, in which case we cannot move it because there may be a reflective access to it.
       for (DexProgramClass clazz : pkg.classesInPackage()) {
         if (clazz.getAccessFlags().isPackagePrivateOrProtected()
-            && appView.getKeepInfo().getClassInfo(clazz).isPinned()) {
+            && appView.getKeepInfo().getClassInfo(clazz).isPinned(options)) {
           return true;
         }
         for (DexEncodedMember<?, ?> member : clazz.members()) {
           if (member.getAccessFlags().isPackagePrivateOrProtected()
-              && appView.getKeepInfo().getMemberInfo(member, clazz).isPinned()) {
+              && appView.getKeepInfo().getMemberInfo(member, clazz).isPinned(options)) {
             return true;
           }
         }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index a013212..27247d4 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -19,11 +19,11 @@
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MemberResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.NestHostClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.SuccessfulMemberResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -97,7 +97,7 @@
   }
 
   public ProgramMethod registerMethodReference(DexMethod method) {
-    ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
+    MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
     registerMemberAccess(resolutionResult, false);
     return resolutionResult.isSingleResolution()
         ? resolutionResult.asSingleResolution().getResolvedProgramMethod()
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index ace24131..32654be 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -214,11 +214,13 @@
     if (memberInfo.isSignatureAttributeRemovalAllowed(options)) {
       member.clearGenericSignature();
     }
-    if (!member.getKotlinInfo().isProperty() && memberInfo.isKotlinMetadataRemovalAllowed(clazz)) {
+    if (!member.getKotlinInfo().isProperty()
+        && memberInfo.isKotlinMetadataRemovalAllowed(clazz, options)) {
       member.clearKotlinInfo();
     }
     // Postpone removal of kotlin property info until we have seen all fields, setters and getters.
-    if (member.getKotlinInfo().isProperty() && !memberInfo.isKotlinMetadataRemovalAllowed(clazz)) {
+    if (member.getKotlinInfo().isProperty()
+        && !memberInfo.isKotlinMetadataRemovalAllowed(clazz, options)) {
       pinnedKotlinProperties.add(member.getKotlinInfo().asProperty());
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 68b7e13..aef58b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -6,7 +6,7 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
 import static com.android.tools.r8.graph.DexEncodedMethod.toMethodDefinitionOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
+import static com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult.isOverriding;
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
@@ -37,18 +37,20 @@
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -371,7 +373,7 @@
   }
 
   private boolean verify() {
-    assert keepInfo.verifyPinnedTypesAreLive(liveTypes);
+    assert keepInfo.verifyPinnedTypesAreLive(liveTypes, options());
     assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
         liveTypes, getMissingClasses(), this);
     return true;
@@ -435,7 +437,7 @@
               DexProgramClass clazz =
                   asProgramClassOrNull(previous.definitionFor(reference.asDexType()));
               if (clazz != null) {
-                collection.pinClass(clazz);
+                collection.joinClass(clazz, Joiner::disallowShrinking);
               }
             } else if (reference.isDexMethod()) {
               DexMethod method = reference.asDexMethod();
@@ -443,7 +445,7 @@
               if (clazz != null) {
                 ProgramMethod definition = clazz.lookupProgramMethod(method);
                 if (definition != null) {
-                  collection.pinMethod(definition);
+                  collection.joinMethod(definition, Joiner::disallowShrinking);
                 }
               }
             } else {
@@ -452,7 +454,7 @@
               if (clazz != null) {
                 ProgramField definition = clazz.lookupProgramField(field);
                 if (definition != null) {
-                  collection.pinField(definition);
+                  collection.joinField(definition, Joiner::disallowShrinking);
                 }
               }
             }
@@ -519,8 +521,8 @@
             || deadProtoTypes.contains(type)
             || getMissingClasses().contains(type)
             // TODO(b/150693139): Remove these exceptions once fixed.
-            || InterfaceMethodRewriter.isCompanionClassType(type)
-            || InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
+            || InterfaceDesugaringSyntheticHelper.isCompanionClassType(type)
+            || InterfaceDesugaringSyntheticHelper.isEmulatedLibraryClassType(type)
             // TODO(b/150736225): Not sure how to remove these.
             || DesugaredLibraryAPIConverter.isVivifiedType(type)
         : "Failed lookup of non-missing type: " + type;
@@ -601,6 +603,10 @@
     return methodsTargetedByInvokeDynamic.contains(method);
   }
 
+  public boolean isMethodTargetedByInvokeDynamic(ProgramMethod method) {
+    return isMethodTargetedByInvokeDynamic(method.getReference());
+  }
+
   public Set<DexMethod> getVirtualMethodsTargetedByInvokeDirect() {
     return virtualMethodsTargetedByInvokeDirect;
   }
@@ -807,6 +813,19 @@
     return isInstantiatedDirectly(clazz) || isInstantiatedIndirectly(clazz);
   }
 
+  public boolean isReachableOrReferencedField(DexEncodedField field) {
+    assert checkIfObsolete();
+    DexField reference = field.getReference();
+    FieldAccessInfo info = getFieldAccessInfoCollection().get(reference);
+    if (info != null) {
+      assert info.isRead() || info.isWritten();
+      return true;
+    }
+    // TODO(b/192924387): When we enqueue a field as a root item, we should maybe create a
+    //  FieldAccessInfo that describes the field is read and written using reflection.
+    return !getKeepInfo().getFieldInfo(reference, this).isShrinkingAllowed(options());
+  }
+
   public boolean isFieldRead(DexEncodedField encodedField) {
     assert checkIfObsolete();
     DexField field = encodedField.getReference();
@@ -814,7 +833,7 @@
     if (info != null && info.isRead()) {
       return true;
     }
-    if (keepInfo.isPinned(field, this)) {
+    if (isPinned(field)) {
       return true;
     }
     // For library classes we don't know whether a field is read.
@@ -890,7 +909,7 @@
     return method.getDefinition().hasCode()
         && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
         && !neverReprocess.contains(reference)
-        && !keepInfo.getMethodInfo(method).isPinned();
+        && !keepInfo.getMethodInfo(method).isPinned(options());
   }
 
   public boolean mayPropagateValueFor(DexClassAndMember<?, ?> member) {
@@ -951,6 +970,10 @@
         && keepInfo.getInfo(reference, this).isMinificationAllowed(options());
   }
 
+  public boolean isAccessModificationAllowed(ProgramDefinition definition) {
+    return isAccessModificationAllowed(definition.getReference());
+  }
+
   public boolean isAccessModificationAllowed(DexReference reference) {
     assert options().getProguardConfiguration().isAccessModificationAllowed();
     return keepInfo.getInfo(reference, this).isAccessModificationAllowed(options());
@@ -960,7 +983,7 @@
     if (!options().isRepackagingEnabled()) {
       return false;
     }
-    if (!keepInfo.getInfo(clazz).isRepackagingAllowed(options())) {
+    if (!keepInfo.getInfo(clazz).isRepackagingAllowed(clazz, options())) {
       return false;
     }
     for (DexType superType : clazz.allImmediateSupertypes()) {
@@ -971,7 +994,7 @@
     return clazz
         .traverseProgramMembers(
             member -> {
-              if (keepInfo.getInfo(member).isRepackagingAllowed(options())) {
+              if (keepInfo.getInfo(member).isRepackagingAllowed(member, options())) {
                 return TraversalContinuation.CONTINUE;
               }
               return TraversalContinuation.BREAK;
@@ -981,7 +1004,7 @@
 
   public boolean isPinned(DexReference reference) {
     assert checkIfObsolete();
-    return keepInfo.isPinned(reference, this);
+    return keepInfo.isPinned(reference, this, options());
   }
 
   public boolean isPinned(DexDefinition definition) {
@@ -1070,7 +1093,7 @@
         methodAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
         objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens),
         lens.rewriteCallSites(callSites, definitionSupplier),
-        keepInfo.rewrite(lens, application.options),
+        keepInfo.rewrite(definitionSupplier, lens, application.options),
         // Take any rule in case of collisions.
         lens.rewriteReferenceKeys(mayHaveSideEffects, ListUtils::first),
         // Drop assume rules in case of collisions.
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultTreePrunerConfiguration.java b/src/main/java/com/android/tools/r8/shaking/DefaultTreePrunerConfiguration.java
index c33ec55..b02ea79 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultTreePrunerConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultTreePrunerConfiguration.java
@@ -19,6 +19,6 @@
 
   @Override
   public boolean isReachableOrReferencedField(AppInfoWithLiveness appInfo, DexEncodedField field) {
-    return appInfo.isFieldRead(field) || appInfo.isFieldWritten(field);
+    return appInfo.isReachableOrReferencedField(field);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
new file mode 100644
index 0000000..aca76da
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DependentMinimumKeepInfoCollection.java
@@ -0,0 +1,166 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.EnqueuerEvent.ClassEnqueuerEvent;
+import com.android.tools.r8.shaking.EnqueuerEvent.UnconditionalKeepInfoEvent;
+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.function.BiConsumer;
+import java.util.function.Function;
+
+public class DependentMinimumKeepInfoCollection {
+
+  private final Map<EnqueuerEvent, MinimumKeepInfoCollection> dependentMinimumKeepInfo;
+
+  public DependentMinimumKeepInfoCollection() {
+    this(new HashMap<>());
+  }
+
+  private DependentMinimumKeepInfoCollection(
+      Map<EnqueuerEvent, MinimumKeepInfoCollection> dependentMinimumKeepInfo) {
+    this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
+  }
+
+  public void forEach(BiConsumer<EnqueuerEvent, MinimumKeepInfoCollection> consumer) {
+    dependentMinimumKeepInfo.forEach(consumer);
+  }
+
+  public void forEach(
+      DexDefinitionSupplier definitions,
+      TriConsumer<EnqueuerEvent, DexProgramClass, KeepClassInfo.Joiner> classConsumer,
+      TriConsumer<EnqueuerEvent, ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
+      TriConsumer<EnqueuerEvent, ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
+    dependentMinimumKeepInfo.forEach(
+        (preconditionEvent, minimumKeepInfo) ->
+            minimumKeepInfo.forEach(
+                definitions,
+                (clazz, minimumKeepInfoForClass) ->
+                    classConsumer.accept(preconditionEvent, clazz, minimumKeepInfoForClass),
+                (field, minimumKeepInfoForField) ->
+                    fieldConsumer.accept(preconditionEvent, field, minimumKeepInfoForField),
+                (method, minimumKeepInfoForMethod) ->
+                    methodConsumer.accept(preconditionEvent, method, minimumKeepInfoForMethod)));
+  }
+
+  public MinimumKeepInfoCollection get(EnqueuerEvent preconditionEvent) {
+    return dependentMinimumKeepInfo.get(preconditionEvent);
+  }
+
+  public MinimumKeepInfoCollection getOrCreateMinimumKeepInfoFor(EnqueuerEvent preconditionEvent) {
+    return dependentMinimumKeepInfo.computeIfAbsent(
+        preconditionEvent, ignoreKey(MinimumKeepInfoCollection::new));
+  }
+
+  public Joiner<?, ?, ?> getOrCreateMinimumKeepInfoFor(
+      EnqueuerEvent preconditionEvent, DexReference reference) {
+    return getOrCreateMinimumKeepInfoFor(preconditionEvent)
+        .getOrCreateMinimumKeepInfoFor(reference);
+  }
+
+  public MinimumKeepInfoCollection getOrCreateUnconditionalMinimumKeepInfo() {
+    return getOrCreateMinimumKeepInfoFor(UnconditionalKeepInfoEvent.get());
+  }
+
+  public Joiner<?, ?, ?> getOrCreateUnconditionalMinimumKeepInfoFor(DexReference reference) {
+    return getOrCreateMinimumKeepInfoFor(UnconditionalKeepInfoEvent.get(), reference);
+  }
+
+  public MinimumKeepInfoCollection getOrDefault(
+      EnqueuerEvent preconditionEvent, MinimumKeepInfoCollection defaultValue) {
+    return dependentMinimumKeepInfo.getOrDefault(preconditionEvent, defaultValue);
+  }
+
+  public MinimumKeepInfoCollection getUnconditionalMinimumKeepInfoOrDefault(
+      MinimumKeepInfoCollection defaultValue) {
+    return getOrDefault(UnconditionalKeepInfoEvent.get(), defaultValue);
+  }
+
+  public void merge(DependentMinimumKeepInfoCollection otherDependentMinimumKeepInfo) {
+    otherDependentMinimumKeepInfo.forEach(
+        (preconditionEvent, minimumKeepInfo) ->
+            getOrCreateMinimumKeepInfoFor(preconditionEvent).merge(minimumKeepInfo));
+  }
+
+  public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+    MapUtils.removeIf(
+        dependentMinimumKeepInfo,
+        (preconditionEvent, minimumKeepInfo) -> {
+          // Check if the precondition refers to a pruned type.
+          if (preconditionEvent.isClassEvent()) {
+            ClassEnqueuerEvent classPreconditionEvent = preconditionEvent.asClassEvent();
+            DexClass clazz = definitions.definitionFor(classPreconditionEvent.getType());
+            if (clazz == null || !enqueuer.isReachable(clazz)) {
+              return true;
+            }
+          } else {
+            assert preconditionEvent.isUnconditionalKeepInfoEvent();
+          }
+
+          // Prune the consequent minimum keep info.
+          assert !minimumKeepInfo.isEmpty();
+          minimumKeepInfo.pruneDeadItems(definitions, enqueuer);
+
+          // If the consequent minimum keep info ended up empty, then remove the preconditionEvent
+          // from the dependent minimum keep info collection.
+          return minimumKeepInfo.isEmpty();
+        });
+  }
+
+  public MinimumKeepInfoCollection remove(EnqueuerEvent preconditionEvent) {
+    return dependentMinimumKeepInfo.remove(preconditionEvent);
+  }
+
+  public KeepClassInfo.Joiner remove(EnqueuerEvent preconditionEvent, DexType clazz) {
+    return internalRemove(preconditionEvent, minimumKeepInfo -> minimumKeepInfo.remove(clazz));
+  }
+
+  public KeepFieldInfo.Joiner remove(EnqueuerEvent preconditionEvent, DexField field) {
+    return internalRemove(preconditionEvent, minimumKeepInfo -> minimumKeepInfo.remove(field));
+  }
+
+  public KeepMethodInfo.Joiner remove(EnqueuerEvent preconditionEvent, DexMethod method) {
+    return internalRemove(preconditionEvent, minimumKeepInfo -> minimumKeepInfo.remove(method));
+  }
+
+  private <J extends Joiner<?, ?, ?>> J internalRemove(
+      EnqueuerEvent preconditionEvent, Function<MinimumKeepInfoCollection, J> fn) {
+    MinimumKeepInfoCollection minimumKeepInfo = get(preconditionEvent);
+    if (minimumKeepInfo == null) {
+      return null;
+    }
+    J minimumKeepInfoForReference = fn.apply(minimumKeepInfo);
+    if (minimumKeepInfo.isEmpty()) {
+      remove(preconditionEvent);
+    }
+    return minimumKeepInfoForReference;
+  }
+
+  public DependentMinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens) {
+    DependentMinimumKeepInfoCollection rewrittenDependentMinimumKeepInfo =
+        new DependentMinimumKeepInfoCollection();
+    forEach(
+        (preconditionEvent, minimumKeepInfo) ->
+            rewrittenDependentMinimumKeepInfo
+                .getOrCreateMinimumKeepInfoFor(preconditionEvent.rewrittenWithLens(graphLens))
+                .merge(minimumKeepInfo.rewrittenWithLens(graphLens)));
+    return rewrittenDependentMinimumKeepInfo;
+  }
+}
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 645d5b9..ba6fb3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -6,12 +6,11 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.emulateInterfaceLibraryMethod;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.getEmulateLibraryInterfaceClassType;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static java.util.Collections.emptySet;
 
 import com.android.tools.r8.Diagnostic;
@@ -30,6 +29,7 @@
 import com.android.tools.r8.graph.ClassDefinition;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.ClasspathOrLibraryDefinition;
+import com.android.tools.r8.graph.Definition;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -67,21 +67,19 @@
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramDerivedContext;
 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.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.FailedResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.analysis.ApiModelAnalysis;
-import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
@@ -122,8 +120,8 @@
 import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
-import com.android.tools.r8.shaking.RootSetUtils.ItemsWithRules;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBase;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
@@ -139,9 +137,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.collections.ProgramFieldMap;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
-import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
@@ -391,13 +387,8 @@
    * Conditional minimum keep info for classes, fields, and methods, which should only be applied if
    * the outermost {@link EnqueuerEvent} is triggered during tracing (e.g., class X becomes live).
    */
-  private final Map<EnqueuerEvent, Map<DexProgramClass, KeepClassInfo.Joiner>>
-      dependentMinimumKeepClassInfo = new HashMap<>();
-
-  private final Map<EnqueuerEvent, ProgramFieldMap<KeepFieldInfo.Joiner>>
-      dependentMinimumKeepFieldInfo = new HashMap<>();
-  private final Map<EnqueuerEvent, ProgramMethodMap<KeepMethodInfo.Joiner>>
-      dependentMinimumKeepMethodInfo = new HashMap<>();
+  private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo =
+      new DependentMinimumKeepInfoCollection();
 
   /**
    * A set of seen const-class references that serve as an initial lock-candidate set and will
@@ -439,7 +430,6 @@
   private final GraphReporter graphReporter;
 
   private final CfInstructionDesugaringCollection desugaring;
-  private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
   private final ProgramMethodSet pendingDesugaring = ProgramMethodSet.create();
 
   Enqueuer(
@@ -491,13 +481,6 @@
     objectAllocationInfoCollection =
         ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
 
-    if (appView.rewritePrefix.isRewriting() && mode.isInitialTreeShaking()) {
-      desugaredLibraryWrapperAnalysis = new DesugaredLibraryConversionWrapperAnalysis(appView);
-      registerAnalysis(desugaredLibraryWrapperAnalysis);
-      registerInvokeAnalysis(desugaredLibraryWrapperAnalysis);
-    } else {
-      desugaredLibraryWrapperAnalysis = null;
-    }
     apiReferenceLevelCache = AndroidApiReferenceLevelCache.create(appView);
   }
 
@@ -792,28 +775,43 @@
     }
   }
 
-  private void enqueueRootItems(ItemsWithRules items) {
-    items.forEachField(this::enqueueRootField);
-    items.forEachMethod(this::enqueueRootMethod);
-    items.forEachClass(this::enqueueRootClass);
-  }
-
-  // TODO(b/123923324): Verify that root items are present.
-  private void enqueueRootClass(DexType type, Set<ProguardKeepRuleBase> rules) {
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
-    if (clazz != null) {
-      enqueueRootClass(clazz, rules);
+  private void enqueueAllIfNotShrinking() {
+    if (appView.options().isShrinking()) {
+      return;
+    }
+    // Add everything if we are not shrinking.
+    assert appView.options().getProguardConfiguration().getKeepAllRule() != null;
+    ProguardKeepRuleBase keepAllRule =
+        appView.options().getProguardConfiguration().getKeepAllRule();
+    KeepClassInfo.Joiner keepClassInfo =
+        KeepClassInfo.newEmptyJoiner().addRule(keepAllRule).disallowShrinking();
+    KeepFieldInfo.Joiner keepFieldInfo =
+        KeepFieldInfo.newEmptyJoiner().addRule(keepAllRule).disallowShrinking();
+    KeepMethodInfo.Joiner keepMethodInfo =
+        KeepMethodInfo.newEmptyJoiner().addRule(keepAllRule).disallowShrinking();
+    EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (appView.getSyntheticItems().isSyntheticClass(clazz)
+          && !appView.getSyntheticItems().isSubjectToKeepRules(clazz)) {
+        // Don't treat compiler synthesized classes as kept roots.
+        continue;
+      }
+      enqueueClassDueToNoShrinkingRule(clazz, keepClassInfo, preconditionEvent);
+      clazz.forEachProgramField(
+          field -> enqueueFieldDueToNoShrinkingRule(field, keepFieldInfo, preconditionEvent));
+      clazz.forEachProgramMethod(
+          method -> enqueueMethodDueToNoShrinkingRule(method, keepMethodInfo, preconditionEvent));
     }
   }
 
-  private void enqueueRootClass(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
-    enqueueRootClass(clazz, rules, null);
-  }
-
-  private void enqueueRootClass(
-      DexProgramClass clazz, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    keepClassWithRules(clazz, rules);
-    enqueueKeepRuleInstantiatedType(clazz, rules, precondition);
+  private void enqueueClassDueToNoShrinkingRule(
+      DexProgramClass clazz,
+      KeepClassInfo.Joiner minimumKeepInfo,
+      EnqueuerEvent preconditionEvent) {
+    assert !minimumKeepInfo.isShrinkingAllowed();
+    assert !minimumKeepInfo.getRules().isEmpty();
+    DexDefinition precondition = preconditionEvent.getDefinition(appInfo());
+    enqueueKeepRuleInstantiatedType(clazz, minimumKeepInfo.getRules(), precondition);
   }
 
   private void enqueueKeepRuleInstantiatedType(
@@ -839,63 +837,28 @@
     }
   }
 
-  // TODO(b/123923324): Verify that root items are present.
-  private void enqueueRootField(DexField reference, Set<ProguardKeepRuleBase> rules) {
-    DexProgramClass holder =
-        asProgramClassOrNull(appInfo().definitionFor(reference.getHolderType()));
-    if (holder != null) {
-      ProgramField field = holder.lookupProgramField(reference);
-      if (field != null) {
-        enqueueRootField(field, rules);
-      }
-    }
-  }
-
-  private void enqueueRootField(ProgramField field, Set<ProguardKeepRuleBase> rules) {
-    enqueueRootField(field, rules, null);
-  }
-
-  private void enqueueRootField(
-      ProgramField field, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    keepFieldWithRules(field, rules);
+  private void enqueueFieldDueToNoShrinkingRule(
+      ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo, EnqueuerEvent preconditionEvent) {
+    assert !minimumKeepInfo.isShrinkingAllowed();
+    assert !minimumKeepInfo.getRules().isEmpty();
+    DexDefinition precondition = preconditionEvent.getDefinition(appInfo());
     workList.enqueueMarkFieldKeptAction(
-        field, graphReporter.reportKeepField(precondition, rules, field.getDefinition()));
+        field,
+        graphReporter.reportKeepField(
+            precondition, minimumKeepInfo.getRules(), field.getDefinition()));
   }
 
-  // TODO(b/123923324): Verify that root items are present.
-  private void enqueueRootMethod(DexMethod reference, Set<ProguardKeepRuleBase> rules) {
-    DexProgramClass holder =
-        asProgramClassOrNull(appInfo().definitionFor(reference.getHolderType()));
-    if (holder != null) {
-      ProgramMethod method = holder.lookupProgramMethod(reference);
-      if (method != null) {
-        enqueueRootMethod(method, rules, null);
-      }
-    }
-  }
-
-  private void enqueueRootMethod(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
-    enqueueRootMethod(method, rules, null);
-  }
-
-  private void enqueueRootMethod(
-      ProgramMethod method, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    keepMethodWithRules(method, rules);
+  private void enqueueMethodDueToNoShrinkingRule(
+      ProgramMethod method,
+      KeepMethodInfo.Joiner minimumKeepInfo,
+      EnqueuerEvent preconditionEvent) {
+    assert !minimumKeepInfo.isShrinkingAllowed();
+    assert !minimumKeepInfo.getRules().isEmpty();
+    DexDefinition precondition = preconditionEvent.getDefinition(appInfo());
     workList.enqueueMarkMethodKeptAction(
-        method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition()));
-  }
-
-  private void internalEnqueueRootItem(
-      ProgramDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    if (item.isProgramClass()) {
-      enqueueRootClass(item.asProgramClass(), rules, precondition);
-    } else if (item.isProgramField()) {
-      enqueueRootField(item.asProgramField(), rules, precondition);
-    } else if (item.isProgramMethod()) {
-      enqueueRootMethod(item.asProgramMethod(), rules, precondition);
-    } else {
-      throw new IllegalArgumentException(item.toString());
-    }
+        method,
+        graphReporter.reportKeepMethod(
+            precondition, minimumKeepInfo.getRules(), method.getDefinition()));
   }
 
   private void enqueueFirstNonSerializableClassInitializer(
@@ -1861,9 +1824,6 @@
           clazz, deferredParameterAnnotations, annotatedItem -> AnnotatedKind.PARAMETER);
     }
 
-    rootSet.forEachDependentInstanceConstructor(
-        clazz, appView, this::enqueueHolderWithDependentInstanceConstructor);
-    rootSet.forEachDependentStaticMember(clazz, appView, this::enqueueDependentMember);
     compatEnqueueHolderIfDependentNonStaticMember(
         clazz, rootSet.getDependentKeepClassCompatRule(clazz.getType()));
 
@@ -1956,13 +1916,6 @@
     }
   }
 
-  private void enqueueDependentMember(
-      DexDefinition precondition,
-      ProgramMember<?, ?> consequent,
-      Set<ProguardKeepRuleBase> reasons) {
-    internalEnqueueRootItem(consequent, reasons, precondition);
-  }
-
   private void enqueueHolderWithDependentInstanceConstructor(
       ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) {
     DexProgramClass holder = instanceInitializer.getHolder();
@@ -2050,7 +2003,7 @@
       DexMethod method, ProgramDefinition context, KeepReason reason) {
     // Record the references in case they are not program types.
     recordMethodReference(method, context);
-    ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
+    MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
     if (resolutionResult.isFailedResolution()) {
       markFailedMethodResolutionTargets(
           method, resolutionResult.asFailedResolution(), context, reason);
@@ -2061,7 +2014,7 @@
   private SingleResolutionResult resolveMethod(
       DexMethod method, ProgramDefinition context, KeepReason reason, boolean interfaceInvoke) {
     // Record the references in case they are not program types.
-    ResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
     if (resolutionResult.isSingleResolution()) {
       recordMethodReference(
           method, resolutionResult.getResolutionPair().asProgramDerivedContext(context));
@@ -2400,6 +2353,9 @@
 
   void markAnnotationAsInstantiated(DexProgramClass clazz, KeepReasonWitness witness) {
     assert clazz.isAnnotation();
+    if (!objectAllocationInfoCollection.recordInstantiatedAnnotation(clazz, appInfo)) {
+      return;
+    }
     markTypeAsLive(clazz, witness);
     transitionDependentItemsForInstantiatedInterface(clazz);
   }
@@ -2516,7 +2472,8 @@
                                 assert appInfo.isSubtype(currentClass.type, type);
                                 instantiation.apply(subTypeConsumer, lambdaConsumer);
                               },
-                              definition -> keepInfo.isPinned(definition.getReference(), appInfo))
+                              definition ->
+                                  keepInfo.isPinned(definition.getReference(), appInfo, options))
                           .forEach(
                               target ->
                                   markVirtualDispatchTargetAsLive(
@@ -2569,7 +2526,7 @@
   private void markLibraryOrClasspathOverrideLive(
       InstantiatedObject instantiation,
       DexClass libraryOrClasspathClass,
-      ResolutionResult resolution) {
+      MethodResolutionResult resolution) {
     LookupTarget lookup = resolution.lookupVirtualDispatchTarget(instantiation, appInfo);
     if (lookup == null) {
       return;
@@ -2649,7 +2606,6 @@
     } else {
       do {
         // Handle keep rules that are dependent on the class being instantiated.
-        rootSet.forEachDependentNonStaticMember(clazz, appView, this::enqueueDependentMember);
         applyMinimumKeepInfoDependentOn(new InstantiatedClassEnqueuerEvent(clazz));
 
         for (DexType interfaceType : clazz.getInterfaces()) {
@@ -2670,8 +2626,6 @@
 
     while (interfacesToTransition.hasNext()) {
       DexProgramClass interfaceClass = interfacesToTransition.next();
-      rootSet.forEachDependentNonStaticMember(
-          interfaceClass, appView, this::enqueueDependentMember);
       applyMinimumKeepInfoDependentOn(new InstantiatedClassEnqueuerEvent(interfaceClass));
 
       for (DexType indirectInterfaceType : interfaceClass.getInterfaces()) {
@@ -2723,9 +2677,6 @@
     // Update keep info.
     applyMinimumKeepInfo(field);
 
-    // Add all dependent members to the workqueue.
-    enqueueRootItems(rootSet.getDependentItems(field.getDefinition()));
-
     // Notify analyses.
     analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
   }
@@ -2796,6 +2747,10 @@
     return info != null;
   }
 
+  public boolean isFieldReferenced(ProgramField field) {
+    return isFieldReferenced(field.getDefinition());
+  }
+
   public boolean isFieldLive(ProgramField field) {
     return liveFields.contains(field);
   }
@@ -2858,6 +2813,10 @@
     return liveMethods.contains(method);
   }
 
+  public boolean isMethodLive(ProgramMethod method) {
+    return isMethodLive(method.getDefinition());
+  }
+
   public boolean isMethodTargeted(DexEncodedMethod method) {
     return targetedMethods.contains(method);
   }
@@ -2881,6 +2840,29 @@
     return liveNonProgramTypes.contains(clazz);
   }
 
+  public boolean isReachable(Definition definition) {
+    assert definition != null;
+
+    if (definition.isClass()) {
+      return isTypeLive(definition.asClass());
+    }
+
+    assert definition.isMember();
+
+    if (definition.getContextClass().isProgramClass()) {
+      if (definition.isField()) {
+        ProgramField field = definition.asProgramField();
+        return isFieldLive(field) || isFieldReferenced(field);
+      } else {
+        assert definition.isMethod();
+        ProgramMethod method = definition.asProgramMethod();
+        return isMethodLive(method) || isMethodTargeted(method);
+      }
+    }
+
+    return isNonProgramTypeLive(definition.getContextClass());
+  }
+
   public void forAllLiveClasses(Consumer<DexProgramClass> consumer) {
     liveTypes.getItems().forEach(consumer);
   }
@@ -2958,7 +2940,7 @@
             (type, subTypeConsumer, lambdaConsumer) ->
                 objectAllocationInfoCollection.forEachInstantiatedSubType(
                     type, subTypeConsumer, lambdaConsumer, appInfo),
-            definition -> keepInfo.isPinned(definition.getReference(), appInfo))
+            definition -> keepInfo.isPinned(definition.getReference(), appInfo, options))
         .forEach(
             target ->
                 markVirtualDispatchTargetAsLive(
@@ -3027,7 +3009,9 @@
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
       workList.enqueueMarkMethodKeptAction(valuesMethod, reason);
-      keepInfo.joinMethod(valuesMethod, joiner -> joiner.pin().disallowMinification());
+      keepInfo.joinMethod(
+          valuesMethod,
+          joiner -> joiner.disallowMinification().disallowOptimization().disallowShrinking());
       shouldNotBeMinified(valuesMethod);
     }
   }
@@ -3083,7 +3067,7 @@
     assert mode.isMainDexTracing();
     this.rootSet = appView.getMainDexRootSet();
     // Translate the result of root-set computation into enqueuer actions.
-    enqueueRootItems(rootSet.noShrinking);
+    includeMinimumKeepInfo(rootSet);
     trace(executorService, timing);
     options.reporter.failIfPendingErrors();
     // Calculate the automatic main dex list according to legacy multidex constraints.
@@ -3120,43 +3104,27 @@
     }
 
     // Transfer the minimum keep info from the root set into the Enqueuer state.
-    rootSet.forEachMinimumKeepInfo(
-        appView,
-        this::recordDependentMinimumKeepInfo,
-        this::recordDependentMinimumKeepInfo,
-        this::recordDependentMinimumKeepInfo);
+    includeMinimumKeepInfo(rootSet);
 
     if (mode.isInitialTreeShaking()) {
       // This is simulating the effect of the "root set" applied rules.
       // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
       // by iterating the instances.
     } else if (appView.getKeepInfo() != null) {
+      EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
       appView
           .getKeepInfo()
           .forEachRuleInstance(
               appView,
-              this::applyMinimumKeepInfoWhenLive,
-              this::applyMinimumKeepInfoWhenLive,
-              this::applyMinimumKeepInfoWhenLiveOrTargeted);
+              (clazz, minimumKeepInfo) ->
+                  applyMinimumKeepInfoWhenLive(clazz, preconditionEvent, minimumKeepInfo),
+              (field, minimumKeepInfo) ->
+                  applyMinimumKeepInfoWhenLive(field, preconditionEvent, minimumKeepInfo),
+              (method, minimumKeepInfo) ->
+                  applyMinimumKeepInfoWhenLiveOrTargeted(
+                      method, preconditionEvent, minimumKeepInfo));
     }
-    if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
-      enqueueRootItems(rootSet.noShrinking);
-    } else {
-      // Add everything if we are not shrinking.
-      assert appView.options().getProguardConfiguration().getKeepAllRule() != null;
-      ImmutableSet<ProguardKeepRuleBase> keepAllSet =
-          ImmutableSet.of(appView.options().getProguardConfiguration().getKeepAllRule());
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
-        if (appView.getSyntheticItems().isSyntheticClass(clazz)
-            && !appView.getSyntheticItems().isSubjectToKeepRules(clazz)) {
-          // Don't treat compiler synthesized classes as kept roots.
-          continue;
-        }
-        enqueueRootClass(clazz, keepAllSet);
-        clazz.forEachProgramMethod(method -> enqueueRootMethod(method, keepAllSet));
-        clazz.forEachProgramField(field -> enqueueRootField(field, keepAllSet));
-      }
-    }
+    enqueueAllIfNotShrinking();
     trace(executorService, timing);
     options.reporter.failIfPendingErrors();
     finalizeLibraryMethodOverrideInformation();
@@ -3175,26 +3143,48 @@
     return createEnqueuerResult(appInfo);
   }
 
+  private void includeMinimumKeepInfo(RootSetBase rootSet) {
+    rootSet
+        .getDependentMinimumKeepInfo()
+        .forEach(
+            appView,
+            this::recordDependentMinimumKeepInfo,
+            this::recordDependentMinimumKeepInfo,
+            this::recordDependentMinimumKeepInfo);
+  }
+
   private void applyMinimumKeepInfo(DexProgramClass clazz) {
-    Map<DexProgramClass, KeepClassInfo.Joiner> minimumKeepClassInfo =
-        dependentMinimumKeepClassInfo.get(UnconditionalKeepInfoEvent.get());
-    if (minimumKeepClassInfo != null) {
-      KeepClassInfo.Joiner minimumKeepInfoForClass = minimumKeepClassInfo.remove(clazz);
-      if (minimumKeepInfoForClass != null) {
-        keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfoForClass));
-      }
+    EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
+    KeepClassInfo.Joiner minimumKeepInfoForClass =
+        dependentMinimumKeepInfo.remove(preconditionEvent, clazz.getType());
+    if (minimumKeepInfoForClass != null) {
+      keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfoForClass));
+      enqueueClassIfShrinkingIsDisallowed(clazz, preconditionEvent, minimumKeepInfoForClass);
     }
   }
 
   private void applyMinimumKeepInfoWhenLive(
-      DexProgramClass clazz, KeepClassInfo.Joiner minimumKeepInfo) {
+      DexProgramClass clazz,
+      EnqueuerEvent preconditionEvent,
+      KeepClassInfo.Joiner minimumKeepInfo) {
     if (liveTypes.contains(clazz)) {
       keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfo));
     } else {
-      dependentMinimumKeepClassInfo
-          .computeIfAbsent(UnconditionalKeepInfoEvent.get(), ignoreKey(IdentityHashMap::new))
-          .computeIfAbsent(clazz, ignoreKey(KeepClassInfo::newEmptyJoiner))
-          .merge(minimumKeepInfo);
+      dependentMinimumKeepInfo
+          .getOrCreateUnconditionalMinimumKeepInfo()
+          .mergeMinimumKeepInfoFor(clazz.getType(), minimumKeepInfo);
+    }
+    enqueueClassIfShrinkingIsDisallowed(clazz, preconditionEvent, minimumKeepInfo);
+  }
+
+  private void enqueueClassIfShrinkingIsDisallowed(
+      DexProgramClass clazz,
+      EnqueuerEvent preconditionEvent,
+      KeepClassInfo.Joiner minimumKeepInfo) {
+    if ((options.isShrinking() || mode.isMainDexTracing())
+        && !minimumKeepInfo.isShrinkingAllowed()) {
+      assert !minimumKeepInfo.getRules().isEmpty();
+      enqueueClassDueToNoShrinkingRule(clazz, minimumKeepInfo, preconditionEvent);
     }
   }
 
@@ -3203,70 +3193,98 @@
       DexProgramClass clazz,
       KeepClassInfo.Joiner minimumKeepInfo) {
     if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
-      applyMinimumKeepInfoWhenLive(clazz, minimumKeepInfo);
+      applyMinimumKeepInfoWhenLive(clazz, preconditionEvent, minimumKeepInfo);
     } else {
-      dependentMinimumKeepClassInfo
-          .computeIfAbsent(preconditionEvent, ignoreKey(IdentityHashMap::new))
-          .computeIfAbsent(clazz, ignoreKey(KeepClassInfo::newEmptyJoiner))
-          .merge(minimumKeepInfo);
+      dependentMinimumKeepInfo
+          .getOrCreateMinimumKeepInfoFor(preconditionEvent)
+          .mergeMinimumKeepInfoFor(clazz.getType(), minimumKeepInfo);
+    }
+    if (preconditionEvent.isUnconditionalKeepInfoEvent()) {
+      enqueueClassIfShrinkingIsDisallowed(clazz, preconditionEvent, minimumKeepInfo);
     }
   }
 
   private void applyMinimumKeepInfo(ProgramField field) {
-    ProgramFieldMap<KeepFieldInfo.Joiner> minimumKeepFieldInfo =
-        dependentMinimumKeepFieldInfo.get(UnconditionalKeepInfoEvent.get());
-    if (minimumKeepFieldInfo != null) {
-      KeepFieldInfo.Joiner minimumKeepInfoForField = minimumKeepFieldInfo.remove(field);
+    EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
+    KeepFieldInfo.Joiner minimumKeepInfoForField =
+        dependentMinimumKeepInfo.remove(preconditionEvent, field.getReference());
       if (minimumKeepInfoForField != null) {
         keepInfo.joinField(field, info -> info.merge(minimumKeepInfoForField));
+        enqueueFieldIfShrinkingIsDisallowed(field, preconditionEvent, minimumKeepInfoForField);
       }
-    }
   }
 
   private void applyMinimumKeepInfoWhenLive(
-      ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
+      ProgramField field, EnqueuerEvent preconditionEvent, KeepFieldInfo.Joiner minimumKeepInfo) {
     if (liveFields.contains(field)) {
       keepInfo.joinField(field, info -> info.merge(minimumKeepInfo));
     } else {
-      dependentMinimumKeepFieldInfo
-          .computeIfAbsent(UnconditionalKeepInfoEvent.get(), ignoreKey(ProgramFieldMap::create))
-          .computeIfAbsent(field, ignoreKey(KeepFieldInfo::newEmptyJoiner))
-          .merge(minimumKeepInfo);
+      dependentMinimumKeepInfo
+          .getOrCreateUnconditionalMinimumKeepInfo()
+          .mergeMinimumKeepInfoFor(field.getReference(), minimumKeepInfo);
+    }
+    enqueueFieldIfShrinkingIsDisallowed(field, preconditionEvent, minimumKeepInfo);
+  }
+
+  private void enqueueFieldIfShrinkingIsDisallowed(
+      ProgramField field, EnqueuerEvent preconditionEvent, KeepFieldInfo.Joiner minimumKeepInfo) {
+    if ((options.isShrinking() || mode.isMainDexTracing())
+        && !minimumKeepInfo.isShrinkingAllowed()) {
+      assert !minimumKeepInfo.getRules().isEmpty();
+      enqueueFieldDueToNoShrinkingRule(field, minimumKeepInfo, preconditionEvent);
     }
   }
 
   private void recordDependentMinimumKeepInfo(
       EnqueuerEvent preconditionEvent, ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo) {
     if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
-      applyMinimumKeepInfoWhenLive(field, minimumKeepInfo);
+      applyMinimumKeepInfoWhenLive(field, preconditionEvent, minimumKeepInfo);
     } else {
-      dependentMinimumKeepFieldInfo
-          .computeIfAbsent(preconditionEvent, ignoreKey(ProgramFieldMap::create))
-          .computeIfAbsent(field, ignoreKey(KeepFieldInfo::newEmptyJoiner))
-          .merge(minimumKeepInfo);
+      dependentMinimumKeepInfo
+          .getOrCreateMinimumKeepInfoFor(preconditionEvent)
+          .mergeMinimumKeepInfoFor(field.getReference(), minimumKeepInfo);
+    }
+    if (preconditionEvent.isUnconditionalKeepInfoEvent()) {
+      enqueueFieldIfShrinkingIsDisallowed(field, preconditionEvent, minimumKeepInfo);
     }
   }
 
   private void applyMinimumKeepInfo(ProgramMethod method) {
-    ProgramMethodMap<KeepMethodInfo.Joiner> minimumKeepMethodInfo =
-        dependentMinimumKeepMethodInfo.get(UnconditionalKeepInfoEvent.get());
-    if (minimumKeepMethodInfo != null) {
-      KeepMethodInfo.Joiner minimumKeepInfoForMethod = minimumKeepMethodInfo.remove(method);
+    EnqueuerEvent preconditionEvent = UnconditionalKeepInfoEvent.get();
+    KeepMethodInfo.Joiner minimumKeepInfoForMethod =
+        dependentMinimumKeepInfo.remove(preconditionEvent, method.getReference());
       if (minimumKeepInfoForMethod != null) {
         keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfoForMethod));
+        enqueueMethodIfShrinkingIsDisallowed(method, preconditionEvent, minimumKeepInfoForMethod);
       }
-    }
   }
 
   private void applyMinimumKeepInfoWhenLiveOrTargeted(
-      ProgramMethod method, KeepMethodInfo.Joiner minimumKeepInfo) {
+      ProgramMethod method,
+      EnqueuerEvent preconditionEvent,
+      KeepMethodInfo.Joiner minimumKeepInfo) {
     if (liveMethods.contains(method) || targetedMethods.contains(method)) {
       keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfo));
     } else {
-      dependentMinimumKeepMethodInfo
-          .computeIfAbsent(UnconditionalKeepInfoEvent.get(), ignoreKey(ProgramMethodMap::create))
-          .computeIfAbsent(method, ignoreKey(KeepMethodInfo::newEmptyJoiner))
-          .merge(minimumKeepInfo);
+      dependentMinimumKeepInfo
+          .getOrCreateUnconditionalMinimumKeepInfo()
+          .mergeMinimumKeepInfoFor(method.getReference(), minimumKeepInfo);
+    }
+    enqueueMethodIfShrinkingIsDisallowed(method, preconditionEvent, minimumKeepInfo);
+  }
+
+  private void enqueueMethodIfShrinkingIsDisallowed(
+      ProgramMethod method,
+      EnqueuerEvent preconditionEvent,
+      KeepMethodInfo.Joiner minimumKeepInfo) {
+    if ((options.isShrinking() || mode.isMainDexTracing())
+        && !minimumKeepInfo.isShrinkingAllowed()) {
+      assert !minimumKeepInfo.getRules().isEmpty();
+      enqueueMethodDueToNoShrinkingRule(method, minimumKeepInfo, preconditionEvent);
+
+      if (method.getDefinition().isInstanceInitializer()) {
+        enqueueHolderWithDependentInstanceConstructor(method, minimumKeepInfo.getRules());
+      }
     }
   }
 
@@ -3275,72 +3293,31 @@
       ProgramMethod method,
       KeepMethodInfo.Joiner minimumKeepInfo) {
     if (isPreconditionForMinimumKeepInfoSatisfied(preconditionEvent)) {
-      applyMinimumKeepInfoWhenLiveOrTargeted(method, minimumKeepInfo);
+      applyMinimumKeepInfoWhenLiveOrTargeted(method, preconditionEvent, minimumKeepInfo);
     } else {
-      dependentMinimumKeepMethodInfo
-          .computeIfAbsent(preconditionEvent, ignoreKey(ProgramMethodMap::create))
-          .computeIfAbsent(method, ignoreKey(KeepMethodInfo::newEmptyJoiner))
-          .merge(minimumKeepInfo);
+      dependentMinimumKeepInfo
+          .getOrCreateMinimumKeepInfoFor(preconditionEvent)
+          .mergeMinimumKeepInfoFor(method.getReference(), minimumKeepInfo);
+    }
+
+    if (preconditionEvent.isUnconditionalKeepInfoEvent()) {
+      enqueueMethodIfShrinkingIsDisallowed(method, preconditionEvent, minimumKeepInfo);
     }
   }
 
-  private void applyMinimumKeepInfoDependentOn(EnqueuerEvent precondition) {
-    Map<DexProgramClass, KeepClassInfo.Joiner> minimumKeepClassInfoDependentOnPrecondition =
-        dependentMinimumKeepClassInfo.remove(precondition);
+  private void applyMinimumKeepInfoDependentOn(EnqueuerEvent preconditionEvent) {
+    MinimumKeepInfoCollection minimumKeepClassInfoDependentOnPrecondition =
+        dependentMinimumKeepInfo.remove(preconditionEvent);
     if (minimumKeepClassInfoDependentOnPrecondition != null) {
-      minimumKeepClassInfoDependentOnPrecondition.forEach(this::applyMinimumKeepInfoWhenLive);
-    }
-
-    ProgramFieldMap<KeepFieldInfo.Joiner> minimumKeepFieldInfoDependentOnPrecondition =
-        dependentMinimumKeepFieldInfo.remove(precondition);
-    if (minimumKeepFieldInfoDependentOnPrecondition != null) {
-      minimumKeepFieldInfoDependentOnPrecondition.forEach(this::applyMinimumKeepInfoWhenLive);
-    }
-
-    ProgramMethodMap<KeepMethodInfo.Joiner> minimumKeepMethodInfoDependentOnPrecondition =
-        dependentMinimumKeepMethodInfo.remove(precondition);
-    if (minimumKeepMethodInfoDependentOnPrecondition != null) {
-      minimumKeepMethodInfoDependentOnPrecondition.forEach(
-          this::applyMinimumKeepInfoWhenLiveOrTargeted);
-    }
-  }
-
-  private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinClass(clazz, info -> applyKeepRules(clazz, rules, info));
-  }
-
-  private void keepFieldWithRules(ProgramField field, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinField(field, info -> applyKeepRules(field, rules, info));
-  }
-
-  private void keepMethodWithRules(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
-    keepInfo.joinMethod(method, info -> applyKeepRules(method, rules, info));
-  }
-
-  private void applyKeepRules(
-      ProgramDefinition definition,
-      Set<ProguardKeepRuleBase> rules,
-      KeepInfo.Joiner<?, ?, ?> joiner) {
-    for (ProguardKeepRuleBase rule : rules) {
-      ProguardKeepRuleModifiers modifiers =
-          (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
-              .getModifiers();
-      if (!modifiers.allowsShrinking) {
-        // TODO(b/159589281): Evaluate this interpretation.
-        joiner.pin();
-        if (definition.getAccessFlags().isPackagePrivateOrProtected()) {
-          joiner.requireAccessModificationForRepackaging();
-        }
-      }
-      if (!modifiers.allowsAccessModification) {
-        joiner.disallowAccessModification();
-      }
-      if (!modifiers.allowsAnnotationRemoval) {
-        joiner.disallowAnnotationRemoval();
-      }
-      if (!modifiers.allowsObfuscation) {
-        joiner.disallowMinification();
-      }
+      minimumKeepClassInfoDependentOnPrecondition.forEach(
+          appView,
+          (clazz, minimumKeepInfoForClass) ->
+              applyMinimumKeepInfoWhenLive(clazz, preconditionEvent, minimumKeepInfoForClass),
+          (field, minimumKeepInfoForField) ->
+              applyMinimumKeepInfoWhenLive(field, preconditionEvent, minimumKeepInfoForField),
+          (method, minimumKeepInfoForMethod) ->
+              applyMinimumKeepInfoWhenLiveOrTargeted(
+                  method, preconditionEvent, minimumKeepInfoForMethod));
     }
   }
 
@@ -3452,7 +3429,6 @@
     SyntheticAdditions additions = new SyntheticAdditions(appView.createProcessorContext());
     desugar(additions);
     synthesizeInterfaceMethodBridges(additions);
-    synthesizeLibraryConversionWrappers(additions);
     if (additions.isEmpty()) {
       return;
     }
@@ -3512,7 +3488,8 @@
       DexProgramClass holder = bridge.getHolder();
       DexEncodedMethod method = bridge.getDefinition();
       holder.addVirtualMethod(method);
-      additions.addLiveMethodWithKeepAction(bridge, KeepMethodInfo.Joiner::pin);
+      additions.addLiveMethodWithKeepAction(
+          bridge, joiner -> joiner.disallowOptimization().disallowShrinking());
     }
     syntheticInterfaceMethodBridges.clear();
   }
@@ -3556,6 +3533,10 @@
     // Prune the root set items that turned out to be dead.
     // TODO(b/150736225): Pruning of dead root set items is still incomplete.
     rootSet.pruneDeadItems(appView, this);
+    if (mode.isTreeShaking() && appView.hasMainDexRootSet()) {
+      assert rootSet != appView.getMainDexRootSet();
+      appView.getMainDexRootSet().pruneDeadItems(appView, this);
+    }
 
     // Ensure references from all hard coded factory items.
     appView
@@ -3763,20 +3744,6 @@
     return synthesizedClasses;
   }
 
-  private void synthesizeLibraryConversionWrappers(SyntheticAdditions additions) {
-    if (desugaredLibraryWrapperAnalysis == null) {
-      return;
-    }
-
-    // Generate first the callbacks since they may require extra wrappers.
-    ProgramMethodSet callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
-    additions.addLiveMethods(callbacks);
-
-    // Generate wrappers on classpath so types are defined.
-    desugaredLibraryWrapperAnalysis.generateWrappers(additions::addLiveClasspathClass);
-  }
-
-
   private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
       Set<R> toDescriptorSet(Set<D> set) {
     ImmutableSet.Builder<R> builder = new ImmutableSet.Builder<>();
@@ -3831,7 +3798,7 @@
                   executorService,
                   activeIfRules,
                   consequentSetBuilder);
-          addConsequentRootSet(ifRuleEvaluator.run(), false);
+          addConsequentRootSet(ifRuleEvaluator.run());
           assert getNumberOfLiveItems() == numberOfLiveItemsAfterProcessing;
           if (!workList.isEmpty()) {
             continue;
@@ -3855,7 +3822,11 @@
           continue;
         }
 
-        addConsequentRootSet(computeDelayedInterfaceMethodSyntheticBridges(), true);
+        ConsequentRootSet consequentRootSet = computeDelayedInterfaceMethodSyntheticBridges();
+        addConsequentRootSet(consequentRootSet);
+        rootSet
+            .getDependentMinimumKeepInfo()
+            .merge(consequentRootSet.getDependentMinimumKeepInfo());
         rootSet.delayedRootSetActionItems.clear();
 
         if (!workList.isEmpty()) {
@@ -3894,10 +3865,12 @@
     SyntheticAdditions syntheticAdditions =
         new SyntheticAdditions(appView.createProcessorContext());
 
+    assert workList.isEmpty();
+
     R8PostProcessingDesugaringEventConsumer eventConsumer =
-        CfPostProcessingDesugaringEventConsumer.createForR8(appView, syntheticAdditions);
+        CfPostProcessingDesugaringEventConsumer.createForR8(syntheticAdditions);
     CfPostProcessingDesugaringCollection.create(appView, null, desugaring.getRetargetingInfo())
-        .postProcessingDesugaring(eventConsumer, executorService);
+        .postProcessingDesugaring(liveTypes.items, eventConsumer, executorService);
 
     if (syntheticAdditions.isEmpty()) {
       return;
@@ -3910,7 +3883,7 @@
 
     syntheticAdditions.enqueueWorkItems(this);
 
-    workList = workList.nonPushable(syntheticAdditions.getLiveMethods());
+    workList = workList.nonPushable();
 
     while (!workList.isEmpty()) {
       EnqueuerAction action = workList.poll();
@@ -3927,42 +3900,12 @@
     return result;
   }
 
-  private void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
-    consequentRootSet.forEachClassWithDependentItems(
-        appView,
-        clazz -> {
-          if (isTypeLive(clazz)) {
-            consequentRootSet.forEachDependentInstanceConstructor(
-                clazz, appView, this::enqueueHolderWithDependentInstanceConstructor);
-            consequentRootSet.forEachDependentStaticMember(
-                clazz, appView, this::enqueueDependentMember);
-            if (objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(
-                clazz)) {
-              consequentRootSet.forEachDependentNonStaticMember(
-                  clazz, appView, this::enqueueDependentMember);
-            }
-            compatEnqueueHolderIfDependentNonStaticMember(
-                clazz, consequentRootSet.getDependentKeepClassCompatRule(clazz.type));
-          }
-        });
-    consequentRootSet.forEachMemberWithDependentItems(
-        appView,
-        (member, dependentItems) -> {
-          if (isMemberLive(member)) {
-            enqueueRootItems(dependentItems);
-          }
-        });
-
-    // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
-    rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking);
-
-    enqueueRootItems(consequentRootSet.noShrinking);
-
-    consequentRootSet.forEachMinimumKeepInfo(
-        appView,
-        this::recordDependentMinimumKeepInfo,
-        this::recordDependentMinimumKeepInfo,
-        this::recordDependentMinimumKeepInfo);
+  private void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
+    // TODO(b/132600955): This modifies the root set, but the consequent should not be persistent.
+    //  Instead, the consequent root set should be added to collections that are owned by the
+    //  enqueuer, similar to Enqueuer#dependentMinimumKeepClassInfo.
+    rootSet.addConsequentRootSet(consequentRootSet);
+    includeMinimumKeepInfo(consequentRootSet);
 
     // Check for compatibility rules indicating that the holder must be implicitly kept.
     if (forceProguardCompatibility) {
@@ -3995,7 +3938,7 @@
     ProgramMethod methodToKeep = action.getMethodToKeep();
     ProgramMethod singleTarget = action.getSingleTarget();
     DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
-    if (rootSet.noShrinking.containsMethod(singleTarget.getReference())) {
+    if (rootSet.isShrinkingDisallowedUnconditionally(singleTarget, options)) {
       return;
     }
     if (methodToKeep != singleTarget
@@ -4169,9 +4112,6 @@
       }
     }
 
-    // Add all dependent members to the workqueue.
-    enqueueRootItems(rootSet.getDependentItems(definition));
-
     // Notify analyses.
     analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
   }
@@ -4359,8 +4299,8 @@
       }
       // To ensure we are not moving the class because we cannot prune it when there is a reflective
       // use of it.
-      if (!keepInfo.getClassInfo(clazz).isPinned()) {
-        keepInfo.pinClass(clazz);
+      if (keepInfo.getClassInfo(clazz).isShrinkingAllowed(options)) {
+        keepInfo.joinClass(clazz, joiner -> joiner.disallowOptimization().disallowShrinking());
       }
     } else if (referencedItem.isDexField()) {
       DexField field = referencedItem.asDexField();
@@ -4384,9 +4324,10 @@
         workList.enqueueMarkInstantiatedAction(
             clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
       }
-      if (!keepInfo.getFieldInfo(encodedField, clazz).isPinned()) {
+      if (keepInfo.getFieldInfo(encodedField, clazz).isShrinkingAllowed(options)) {
         ProgramField programField = new ProgramField(clazz, encodedField);
-        keepInfo.pinField(programField);
+        keepInfo.joinField(
+            programField, joiner -> joiner.disallowOptimization().disallowShrinking());
         markFieldAsKept(programField, KeepReason.reflectiveUseIn(method));
       }
     } else {
@@ -4575,7 +4516,7 @@
         // Add this interface to the set of pinned items to ensure that we do not merge the
         // interface into its unique subtype, if any.
         // TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
-        keepInfo.pinClass(clazz);
+        keepInfo.joinClass(clazz, joiner -> joiner.disallowOptimization().disallowShrinking());
         KeepReason reason = KeepReason.reflectiveUseIn(method);
         markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
 
@@ -4583,7 +4524,8 @@
         // illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
         clazz.forEachProgramVirtualMethod(
             virtualMethod -> {
-              keepInfo.pinMethod(virtualMethod);
+              keepInfo.joinMethod(
+                  virtualMethod, joiner -> joiner.disallowOptimization().disallowShrinking());
               markVirtualMethodAsReachable(virtualMethod.getReference(), true, clazz, reason);
             });
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
index ba7ee40..ee2b7d0 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerEvent.java
@@ -4,11 +4,18 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 
 public abstract class EnqueuerEvent {
 
+  public DexDefinition getDefinition(DexDefinitionSupplier definitions) {
+    return null;
+  }
+
   public boolean isClassEvent() {
     return false;
   }
@@ -37,12 +44,19 @@
     return false;
   }
 
+  public abstract EnqueuerEvent rewrittenWithLens(GraphLens lens);
+
   public abstract static class ClassEnqueuerEvent extends EnqueuerEvent {
 
     private final DexType clazz;
 
-    public ClassEnqueuerEvent(DexProgramClass clazz) {
-      this.clazz = clazz.getType();
+    ClassEnqueuerEvent(DexType clazz) {
+      this.clazz = clazz;
+    }
+
+    @Override
+    public DexDefinition getDefinition(DexDefinitionSupplier definitions) {
+      return definitions.definitionFor(getType());
     }
 
     public DexType getType() {
@@ -63,7 +77,11 @@
   public static class LiveClassEnqueuerEvent extends ClassEnqueuerEvent {
 
     public LiveClassEnqueuerEvent(DexProgramClass clazz) {
-      super(clazz);
+      this(clazz.getType());
+    }
+
+    private LiveClassEnqueuerEvent(DexType type) {
+      super(type);
     }
 
     @Override
@@ -77,6 +95,11 @@
     }
 
     @Override
+    public EnqueuerEvent rewrittenWithLens(GraphLens lens) {
+      return new LiveClassEnqueuerEvent(lens.lookupType(getType()));
+    }
+
+    @Override
     public boolean equals(Object obj) {
       if (obj == this) {
         return true;
@@ -97,7 +120,11 @@
   public static class InstantiatedClassEnqueuerEvent extends ClassEnqueuerEvent {
 
     public InstantiatedClassEnqueuerEvent(DexProgramClass clazz) {
-      super(clazz);
+      this(clazz.getType());
+    }
+
+    private InstantiatedClassEnqueuerEvent(DexType type) {
+      super(type);
     }
 
     @Override
@@ -111,6 +138,11 @@
     }
 
     @Override
+    public EnqueuerEvent rewrittenWithLens(GraphLens lens) {
+      return new InstantiatedClassEnqueuerEvent(lens.lookupType(getType()));
+    }
+
+    @Override
     public boolean equals(Object obj) {
       if (obj == this) {
         return true;
@@ -142,5 +174,10 @@
     public boolean isUnconditionalKeepInfoEvent() {
       return true;
     }
+
+    @Override
+    public EnqueuerEvent rewrittenWithLens(GraphLens lens) {
+      return this;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 2fcac55..cd52fd7 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
@@ -321,7 +320,7 @@
     return queue.poll();
   }
 
-  abstract EnqueuerWorklist nonPushable(ProgramMethodSet enqueuedMarkMethodLive);
+  abstract EnqueuerWorklist nonPushable();
 
   abstract boolean enqueueAssertAction(Action assertion);
 
@@ -373,8 +372,8 @@
     }
 
     @Override
-    EnqueuerWorklist nonPushable(ProgramMethodSet enqueuedMarkMethodLive) {
-      return new NonPushableEnqueuerWorklist(this, enqueuedMarkMethodLive);
+    EnqueuerWorklist nonPushable() {
+      return new NonPushableEnqueuerWorklist(this);
     }
 
     @Override
@@ -487,16 +486,12 @@
 
   public static class NonPushableEnqueuerWorklist extends EnqueuerWorklist {
 
-    private ProgramMethodSet enqueuedMarkMethodLive;
-
-    private NonPushableEnqueuerWorklist(
-        PushableEnqueuerWorkList workList, ProgramMethodSet enqueuedMarkMethodLive) {
+    private NonPushableEnqueuerWorklist(PushableEnqueuerWorkList workList) {
       super(workList.enqueuer, workList.queue);
-      this.enqueuedMarkMethodLive = enqueuedMarkMethodLive;
     }
 
     @Override
-    EnqueuerWorklist nonPushable(ProgramMethodSet enqueuedMarkMethodLive) {
+    EnqueuerWorklist nonPushable() {
       return this;
     }
 
@@ -553,7 +548,7 @@
     @Override
     boolean enqueueMarkMethodLiveAction(
         ProgramMethod method, ProgramDefinition context, KeepReason reason) {
-      if (enqueuedMarkMethodLive.contains(method)) {
+      if (!enqueuer.addLiveMethod(method, reason)) {
         return false;
       }
       throw attemptToEnqueue();
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index fabdd04..83055e7 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -27,7 +27,7 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.DequeUtils;
@@ -283,7 +283,7 @@
 
   public KeepReasonWitness reportCompanionClass(DexProgramClass iface, DexProgramClass companion) {
     assert iface.isInterface();
-    assert InterfaceMethodRewriter.isCompanionClassType(companion.type);
+    assert InterfaceDesugaringSyntheticHelper.isCompanionClassType(companion.type);
     if (keptGraphConsumer == null) {
       return KeepReasonWitness.INSTANCE;
     }
@@ -293,7 +293,7 @@
 
   public KeepReasonWitness reportCompanionMethod(
       DexEncodedMethod definition, DexEncodedMethod implementation) {
-    assert InterfaceMethodRewriter.isCompanionClassType(implementation.getHolderType());
+    assert InterfaceDesugaringSyntheticHelper.isCompanionClassType(implementation.getHolderType());
     if (keptGraphConsumer == null) {
       return KeepReasonWitness.INSTANCE;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index dc4a931..bf969b7 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -3,11 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Function;
 
 /** Immutable keep requirements for a class. */
@@ -46,36 +47,32 @@
   }
 
   @Override
-  public boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration) {
+  public boolean isRepackagingAllowed(
+      ProgramDefinition definition, GlobalKeepInfoConfiguration configuration) {
     return configuration.isRepackagingEnabled()
         && internalIsMinificationAllowed()
-        && !internalIsAccessModificationRequiredForRepackaging();
+        && (definition.getAccessFlags().isPublic()
+            || !internalIsAccessModificationRequiredForRepackaging());
   }
 
   public boolean isKotlinMetadataRemovalAllowed(
       GlobalKeepInfoConfiguration configuration, boolean kotlinMetadataKept) {
     return !kotlinMetadataKept
-        || !isPinned()
+        || !isPinned(configuration)
         || !configuration.isKeepRuntimeVisibleAnnotationsEnabled()
         || isAnnotationRemovalAllowed(configuration);
   }
 
-  public static boolean isKotlinMetadataClassKept(AppView<?> appView) {
-    return isKotlinMetadataClassKept(
-        appView.dexItemFactory(),
-        appView.appInfo()::definitionForWithoutExistenceAssert,
-        appView.getKeepInfo()::getClassInfo);
-  }
-
   public static boolean isKotlinMetadataClassKept(
       DexItemFactory factory,
+      InternalOptions options,
       Function<DexType, DexClass> definitionForWithoutExistenceAssert,
       Function<DexProgramClass, KeepClassInfo> getClassInfo) {
     DexType kotlinMetadataType = factory.kotlinMetadataType;
     DexClass kotlinMetadataClass = definitionForWithoutExistenceAssert.apply(kotlinMetadataType);
     return kotlinMetadataClass == null
         || kotlinMetadataClass.isNotProgramClass()
-        || getClassInfo.apply(kotlinMetadataClass.asProgramClass()).isPinned();
+        || !getClassInfo.apply(kotlinMetadataClass.asProgramClass()).isShrinkingAllowed(options);
   }
 
   @Override
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 b795aab..c3165ad 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -4,14 +4,17 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.shaking.KeepInfo.Builder;
+import com.google.common.collect.Sets;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /** Keep information that can be associated with any item, i.e., class, method or field. */
 public abstract class KeepInfo<B extends Builder<B, K>, K extends KeepInfo<B, K>> {
 
-  private final boolean pinned;
   private final boolean allowAccessModification;
   private final boolean allowAnnotationRemoval;
   private final boolean allowMinification;
@@ -20,14 +23,12 @@
   private final boolean requireAccessModificationForRepackaging;
 
   private KeepInfo(
-      boolean pinned,
       boolean allowAccessModification,
       boolean allowAnnotationRemoval,
       boolean allowMinification,
       boolean allowOptimization,
       boolean allowShrinking,
       boolean requireAccessModificationForRepackaging) {
-    this.pinned = pinned;
     this.allowAccessModification = allowAccessModification;
     this.allowAnnotationRemoval = allowAnnotationRemoval;
     this.allowMinification = allowMinification;
@@ -38,7 +39,6 @@
 
   KeepInfo(B builder) {
     this(
-        builder.isPinned(),
         builder.isAccessModificationAllowed(),
         builder.isAnnotationRemovalAllowed(),
         builder.isMinificationAllowed(),
@@ -47,6 +47,13 @@
         builder.isAccessModificationRequiredForRepackaging());
   }
 
+  public static Joiner<?, ?, ?> newEmptyJoinerFor(DexReference reference) {
+    return reference.apply(
+        clazz -> KeepClassInfo.newEmptyJoiner(),
+        field -> KeepFieldInfo.newEmptyJoiner(),
+        method -> KeepMethodInfo.newEmptyJoiner());
+  }
+
   abstract B builder();
 
   /**
@@ -69,8 +76,8 @@
    * @deprecated Prefer task dependent predicates.
    */
   @Deprecated
-  public boolean isPinned() {
-    return pinned || !allowOptimization;
+  public boolean isPinned(GlobalKeepInfoConfiguration configuration) {
+    return !isOptimizationAllowed(configuration) || !isShrinkingAllowed(configuration);
   }
 
   /**
@@ -121,7 +128,8 @@
    * <p>This method requires knowledge of the global configuration as that can override the concrete
    * value on a given item.
    */
-  public abstract boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration);
+  public abstract boolean isRepackagingAllowed(
+      ProgramDefinition definition, GlobalKeepInfoConfiguration configuration);
 
   boolean internalIsAccessModificationRequiredForRepackaging() {
     return requireAccessModificationForRepackaging;
@@ -148,7 +156,7 @@
     if (!configuration.isKeepAttributesSignatureEnabled()) {
       return true;
     }
-    return !(configuration.isForceProguardCompatibilityEnabled() || isPinned());
+    return !(configuration.isForceProguardCompatibilityEnabled() || isPinned(configuration));
   }
 
   public boolean isEnclosingMethodAttributeRemovalAllowed(
@@ -161,14 +169,14 @@
     if (configuration.isForceProguardCompatibilityEnabled()) {
       return false;
     }
-    return !isPinned() || !enclosingMethodAttribute.isEnclosingPinned(appView);
+    return !isPinned(configuration) || !enclosingMethodAttribute.isEnclosingPinned(appView);
   }
 
   public boolean isInnerClassesAttributeRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
     if (!configuration.isKeepInnerClassesAttributeEnabled()) {
       return true;
     }
-    return !(configuration.isForceProguardCompatibilityEnabled() || isPinned());
+    return !(configuration.isForceProguardCompatibilityEnabled() || isPinned(configuration));
   }
 
   public boolean isInnerClassesAttributeRemovalAllowed(
@@ -182,7 +190,7 @@
     }
     // The inner class is dependent on the enclosingMethodAttribute and since it has been pruned
     // we can also remove this inner class relationship.
-    return enclosingMethodAttribute == null || !isPinned();
+    return enclosingMethodAttribute == null || !isPinned(configuration);
   }
 
   public abstract boolean isTop();
@@ -192,8 +200,7 @@
   public boolean isLessThanOrEquals(K other) {
     // An item is less, aka, lower in the lattice, if each of its attributes is at least as
     // permissive of that on other.
-    return (!pinned || other.isPinned())
-        && (allowAccessModification || !other.internalIsAccessModificationAllowed())
+    return (allowAccessModification || !other.internalIsAccessModificationAllowed())
         && (allowAnnotationRemoval || !other.internalIsAnnotationRemovalAllowed())
         && (allowMinification || !other.internalIsMinificationAllowed())
         && (allowOptimization || !other.internalIsOptimizationAllowed())
@@ -214,7 +221,6 @@
     abstract boolean isEqualTo(K other);
 
     private K original;
-    private boolean pinned;
     private boolean allowAccessModification;
     private boolean allowAnnotationRemoval;
     private boolean allowMinification;
@@ -228,7 +234,6 @@
 
     Builder(K original) {
       this.original = original;
-      pinned = original.isPinned();
       allowAccessModification = original.internalIsAccessModificationAllowed();
       allowAnnotationRemoval = original.internalIsAnnotationRemovalAllowed();
       allowMinification = original.internalIsMinificationAllowed();
@@ -239,7 +244,6 @@
     }
 
     B makeTop() {
-      pin();
       disallowAccessModification();
       disallowAnnotationRemoval();
       disallowMinification();
@@ -250,7 +254,6 @@
     }
 
     B makeBottom() {
-      unpin();
       allowAccessModification();
       allowAnnotationRemoval();
       allowMinification();
@@ -276,8 +279,7 @@
     }
 
     boolean internalIsEqualTo(K other) {
-      return isPinned() == other.isPinned()
-          && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
+      return isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
           && isAnnotationRemovalAllowed() == other.internalIsAnnotationRemovalAllowed()
           && isMinificationAllowed() == other.internalIsMinificationAllowed()
           && isOptimizationAllowed() == other.internalIsOptimizationAllowed()
@@ -286,10 +288,6 @@
               == other.internalIsAccessModificationRequiredForRepackaging();
     }
 
-    public boolean isPinned() {
-      return pinned;
-    }
-
     public boolean isAccessModificationRequiredForRepackaging() {
       return requireAccessModificationForRepackaging;
     }
@@ -314,19 +312,6 @@
       return allowShrinking;
     }
 
-    public B setPinned(boolean pinned) {
-      this.pinned = pinned;
-      return self();
-    }
-
-    public B pin() {
-      return setPinned(true);
-    }
-
-    public B unpin() {
-      return setPinned(false);
-    }
-
     public B setAllowMinification(boolean allowMinification) {
       this.allowMinification = allowMinification;
       return self();
@@ -415,6 +400,11 @@
 
     final Builder<B, K> builder;
 
+    // The set of rules that have contributed to this joiner. These are only needed for the
+    // interpretation of keep rules into keep info, and is therefore not stored in the keep info
+    // builder above.
+    final Set<ProguardKeepRuleBase> rules = Sets.newIdentityHashSet();
+
     Joiner(Builder<B, K> builder) {
       this.builder = builder;
     }
@@ -438,10 +428,18 @@
       return null;
     }
 
+    public Set<ProguardKeepRuleBase> getRules() {
+      return rules;
+    }
+
     public boolean isBottom() {
       return builder.isEqualTo(builder.getBottomInfo());
     }
 
+    public boolean isShrinkingAllowed() {
+      return builder.isShrinkingAllowed();
+    }
+
     public boolean isTop() {
       return builder.isEqualTo(builder.getTopInfo());
     }
@@ -451,8 +449,8 @@
       return self();
     }
 
-    public J pin() {
-      builder.pin();
+    public J addRule(ProguardKeepRuleBase rule) {
+      rules.add(rule);
       return self();
     }
 
@@ -488,7 +486,6 @@
 
     public J merge(J joiner) {
       Builder<B, K> builder = joiner.builder;
-      applyIf(builder.isPinned(), Joiner::pin);
       applyIf(!builder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
       applyIf(!builder.isAnnotationRemovalAllowed(), Joiner::disallowAnnotationRemoval);
       applyIf(!builder.isMinificationAllowed(), Joiner::disallowMinification);
@@ -497,9 +494,15 @@
       applyIf(
           builder.isAccessModificationRequiredForRepackaging(),
           Joiner::requireAccessModificationForRepackaging);
+      rules.addAll(joiner.rules);
       return self();
     }
 
+    @SuppressWarnings("unchecked")
+    public J mergeUnsafe(Joiner<?, ?, ?> joiner) {
+      return merge((J) joiner);
+    }
+
     public K join() {
       K joined = builder.build();
       K original = builder.original;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 71068d2..0dcddda 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -152,20 +152,30 @@
     throw new Unreachable();
   }
 
-  public final boolean isPinned(DexReference reference, DexDefinitionSupplier definitions) {
-    return getInfo(reference, definitions).isPinned();
+  public final boolean isPinned(
+      DexReference reference,
+      DexDefinitionSupplier definitions,
+      GlobalKeepInfoConfiguration configuration) {
+    return getInfo(reference, definitions).isPinned(configuration);
   }
 
-  public final boolean isPinned(DexType type, DexDefinitionSupplier definitions) {
-    return getClassInfo(type, definitions).isPinned();
+  public final boolean isPinned(
+      DexType type, DexDefinitionSupplier definitions, GlobalKeepInfoConfiguration configuration) {
+    return getClassInfo(type, definitions).isPinned(configuration);
   }
 
-  public final boolean isPinned(DexMethod method, DexDefinitionSupplier definitions) {
-    return getMethodInfo(method, definitions).isPinned();
+  public final boolean isPinned(
+      DexMethod method,
+      DexDefinitionSupplier definitions,
+      GlobalKeepInfoConfiguration configuration) {
+    return getMethodInfo(method, definitions).isPinned(configuration);
   }
 
-  public final boolean isPinned(DexField field, DexDefinitionSupplier definitions) {
-    return getFieldInfo(field, definitions).isPinned();
+  public final boolean isPinned(
+      DexField field,
+      DexDefinitionSupplier definitions,
+      GlobalKeepInfoConfiguration configuration) {
+    return getFieldInfo(field, definitions).isPinned(configuration);
   }
 
   public final boolean isMinificationAllowed(
@@ -176,21 +186,22 @@
         && getInfo(reference, definitions).isMinificationAllowed(configuration);
   }
 
-  public abstract boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes);
+  public abstract boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes, InternalOptions options);
 
   // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
   @Deprecated
-  public abstract void forEachPinnedType(Consumer<DexType> consumer);
+  public abstract void forEachPinnedType(Consumer<DexType> consumer, InternalOptions options);
 
   // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
   @Deprecated
-  public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer);
+  public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer, InternalOptions options);
 
   // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
   @Deprecated
-  public abstract void forEachPinnedField(Consumer<DexField> consumer);
+  public abstract void forEachPinnedField(Consumer<DexField> consumer, InternalOptions options);
 
-  public abstract KeepInfoCollection rewrite(NonIdentityGraphLens lens, InternalOptions options);
+  public abstract KeepInfoCollection rewrite(
+      DexDefinitionSupplier definitions, NonIdentityGraphLens lens, InternalOptions options);
 
   public abstract KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator);
 
@@ -240,15 +251,17 @@
     }
 
     @Override
-    public KeepInfoCollection rewrite(NonIdentityGraphLens lens, InternalOptions options) {
+    public KeepInfoCollection rewrite(
+        DexDefinitionSupplier definitions, NonIdentityGraphLens lens, InternalOptions options) {
       Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
       keepClassInfo.forEach(
           (type, info) -> {
             DexType newType = lens.lookupType(type);
             assert newType == type
-                || !info.isPinned()
+                || !info.isPinned(options)
                 || info.isMinificationAllowed(options)
-                || info.isRepackagingAllowed(options);
+                || info.isRepackagingAllowed(
+                    definitions.definitionFor(newType).asProgramClass(), options);
             KeepClassInfo previous = newClassInfo.put(newType, info);
             assert previous == null;
           });
@@ -256,17 +269,17 @@
       keepMethodInfo.forEach(
           (method, info) -> {
             DexMethod newMethod = lens.getRenamedMethodSignature(method);
-            assert !info.isPinned()
+            assert !info.isPinned(options)
                 || info.isMinificationAllowed(options)
                 || newMethod.name == method.name;
-            assert !info.isPinned() || newMethod.getArity() == method.getArity();
-            assert !info.isPinned()
+            assert !info.isPinned(options) || newMethod.getArity() == method.getArity();
+            assert !info.isPinned(options)
                 || Streams.zip(
                         newMethod.getParameters().stream(),
                         method.getParameters().stream().map(lens::lookupType),
                         Object::equals)
                     .allMatch(x -> x);
-            assert !info.isPinned()
+            assert !info.isPinned(options)
                 || newMethod.getReturnType() == lens.lookupType(method.getReturnType());
             KeepMethodInfo previous = newMethodInfo.put(newMethod, info);
             // TODO(b/169927809): Avoid collisions.
@@ -277,7 +290,7 @@
           (field, info) -> {
             DexField newField = lens.getRenamedFieldSignature(field);
             assert newField.name == field.name
-                || !info.isPinned()
+                || !info.isPinned(options)
                 || info.isMinificationAllowed(options);
             KeepFieldInfo previous = newFieldInfo.put(newField, info);
             assert previous == null;
@@ -312,7 +325,8 @@
           IdentityHashMap::new,
           rewriter,
           Function.identity(),
-          (joiner, otherJoiner) -> newEmptyJoiner.get().merge(joiner).merge(otherJoiner));
+          (reference, joiner, otherJoiner) ->
+              newEmptyJoiner.get().merge(joiner).merge(otherJoiner));
     }
 
     @Override
@@ -410,10 +424,6 @@
       joinClass(clazz, KeepInfo.Joiner::top);
     }
 
-    public void pinClass(DexProgramClass clazz) {
-      joinClass(clazz, KeepInfo.Joiner::pin);
-    }
-
     public void joinMethod(ProgramMethod method, Consumer<? super KeepMethodInfo.Joiner> fn) {
       KeepMethodInfo info = getMethodInfo(method);
       if (info.isTop()) {
@@ -432,19 +442,6 @@
       joinMethod(method, KeepInfo.Joiner::top);
     }
 
-    public void pinMethod(ProgramMethod method) {
-      joinMethod(method, KeepInfo.Joiner::pin);
-    }
-
-    // TODO(b/157700141): Avoid pinning/unpinning references.
-    @Deprecated
-    public void unsafeUnpinMethod(DexMethod method) {
-      KeepMethodInfo info = keepMethodInfo.get(method);
-      if (info != null && info.isPinned()) {
-        keepMethodInfo.put(method, info.builder().unpin().build());
-      }
-    }
-
     public void unsetRequireAllowAccessModificationForRepackaging(ProgramDefinition definition) {
       if (definition.isProgramClass()) {
         DexProgramClass clazz = definition.asProgramClass();
@@ -486,10 +483,6 @@
       joinField(field, KeepInfo.Joiner::top);
     }
 
-    public void pinField(ProgramField field) {
-      joinField(field, KeepInfo.Joiner::pin);
-    }
-
     @Override
     public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
       mutator.accept(this);
@@ -497,39 +490,39 @@
     }
 
     @Override
-    public boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes) {
+    public boolean verifyPinnedTypesAreLive(Set<DexType> liveTypes, InternalOptions options) {
       keepClassInfo.forEach(
           (type, info) -> {
-            assert !info.isPinned() || liveTypes.contains(type);
+            assert !info.isPinned(options) || liveTypes.contains(type);
           });
       return true;
     }
 
     @Override
-    public void forEachPinnedType(Consumer<DexType> consumer) {
+    public void forEachPinnedType(Consumer<DexType> consumer, InternalOptions options) {
       keepClassInfo.forEach(
           (type, info) -> {
-            if (info.isPinned()) {
+            if (info.isPinned(options)) {
               consumer.accept(type);
             }
           });
     }
 
     @Override
-    public void forEachPinnedMethod(Consumer<DexMethod> consumer) {
+    public void forEachPinnedMethod(Consumer<DexMethod> consumer, InternalOptions options) {
       keepMethodInfo.forEach(
           (method, info) -> {
-            if (info.isPinned()) {
+            if (info.isPinned(options)) {
               consumer.accept(method);
             }
           });
     }
 
     @Override
-    public void forEachPinnedField(Consumer<DexField> consumer) {
+    public void forEachPinnedField(Consumer<DexField> consumer, InternalOptions options) {
       keepFieldInfo.forEach(
           (field, info) -> {
-            if (info.isPinned()) {
+            if (info.isPinned(options)) {
               consumer.accept(field);
             }
           });
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
index 46d8fa2..2225e14 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.shaking.KeepInfo.Builder;
 
 /** Immutable keep requirements for a member. */
@@ -15,14 +16,17 @@
   }
 
   @Override
-  public boolean isRepackagingAllowed(GlobalKeepInfoConfiguration configuration) {
+  public boolean isRepackagingAllowed(
+      ProgramDefinition definition, GlobalKeepInfoConfiguration configuration) {
     return configuration.isRepackagingEnabled()
-        && !internalIsAccessModificationRequiredForRepackaging();
+        && (definition.getAccessFlags().isPublic()
+            || !internalIsAccessModificationRequiredForRepackaging());
   }
 
-  public boolean isKotlinMetadataRemovalAllowed(DexProgramClass holder) {
+  public boolean isKotlinMetadataRemovalAllowed(
+      DexProgramClass holder, GlobalKeepInfoConfiguration configuration) {
     // Checking the holder for missing kotlin information relies on the holder being processed
     // before members.
-    return holder.getKotlinInfo().isNoKotlinInformation() || !isPinned();
+    return holder.getKotlinInfo().isNoKotlinInformation() || !isPinned(configuration);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index 1669a40..dd4226c 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -35,6 +35,10 @@
     return new Builder(this);
   }
 
+  public boolean isArgumentPropagationAllowed(GlobalKeepInfoConfiguration configuration) {
+    return isOptimizationAllowed(configuration);
+  }
+
   public Joiner joiner() {
     assert !isTop();
     return new Joiner(this);
diff --git a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
index d63c685..763f7d4 100644
--- a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
+++ b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
@@ -53,7 +53,8 @@
     appView
         .appInfo()
         .getKeepInfo()
-        .forEachPinnedType(initialNonEscapingClassesWithLibraryMethodOverrides::remove);
+        .forEachPinnedType(
+            initialNonEscapingClassesWithLibraryMethodOverrides::remove, appView.options());
 
     return initialNonEscapingClassesWithLibraryMethodOverrides;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
new file mode 100644
index 0000000..9f97e63
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
@@ -0,0 +1,172 @@
+// 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.shaking;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+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.function.BiConsumer;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+public class MinimumKeepInfoCollection {
+
+  private static final MinimumKeepInfoCollection EMPTY =
+      new MinimumKeepInfoCollection(Collections.emptyMap());
+
+  private final Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo;
+
+  public MinimumKeepInfoCollection() {
+    this(new IdentityHashMap<>());
+  }
+
+  private MinimumKeepInfoCollection(Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo) {
+    this.minimumKeepInfo = minimumKeepInfo;
+  }
+
+  public static MinimumKeepInfoCollection empty() {
+    return EMPTY;
+  }
+
+  public void forEach(BiConsumer<DexReference, KeepInfo.Joiner<?, ?, ?>> consumer) {
+    minimumKeepInfo.forEach(consumer);
+  }
+
+  public void forEach(
+      DexDefinitionSupplier definitions,
+      BiConsumer<DexProgramClass, KeepClassInfo.Joiner> classConsumer,
+      BiConsumer<ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
+      BiConsumer<ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
+    minimumKeepInfo.forEach(
+        (reference, joiner) -> {
+          DexProgramClass contextClass =
+              asProgramClassOrNull(definitions.definitionFor(reference.getContextType()));
+          if (contextClass != null) {
+            reference.accept(
+                clazz -> classConsumer.accept(contextClass, joiner.asClassJoiner()),
+                fieldReference -> {
+                  ProgramField field = contextClass.lookupProgramField(fieldReference);
+                  if (field != null) {
+                    fieldConsumer.accept(field, joiner.asFieldJoiner());
+                  }
+                },
+                methodReference -> {
+                  ProgramMethod method = contextClass.lookupProgramMethod(methodReference);
+                  if (method != null) {
+                    methodConsumer.accept(method, joiner.asMethodJoiner());
+                  }
+                });
+          }
+        });
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T extends DexReference> void forEachThatMatches(
+      BiPredicate<DexReference, Joiner<?, ?, ?>> predicate,
+      BiConsumer<T, KeepInfo.Joiner<?, ?, ?>> consumer) {
+    minimumKeepInfo.forEach(
+        (reference, minimumKeepInfoForReference) -> {
+          if (predicate.test(reference, minimumKeepInfoForReference)) {
+            consumer.accept((T) reference, minimumKeepInfoForReference);
+          }
+        });
+  }
+
+  public KeepInfo.Joiner<?, ?, ?> getOrCreateMinimumKeepInfoFor(DexReference reference) {
+    return minimumKeepInfo.computeIfAbsent(
+        reference, ignoreKey(() -> KeepInfo.newEmptyJoinerFor(reference)));
+  }
+
+  public boolean hasMinimumKeepInfoThatMatches(
+      DexReference reference, Predicate<KeepInfo.Joiner<?, ?, ?>> predicate) {
+    KeepInfo.Joiner<?, ?, ?> minimumKeepInfoForReference = minimumKeepInfo.get(reference);
+    return minimumKeepInfoForReference != null && predicate.test(minimumKeepInfoForReference);
+  }
+
+  public boolean isEmpty() {
+    return minimumKeepInfo.isEmpty();
+  }
+
+  public void merge(MinimumKeepInfoCollection otherMinimumKeepInfo) {
+    otherMinimumKeepInfo.forEach(this::mergeMinimumKeepInfoFor);
+  }
+
+  public void mergeMinimumKeepInfoFor(
+      DexReference reference, KeepInfo.Joiner<?, ?, ?> minimumKeepInfoForReference) {
+    getOrCreateMinimumKeepInfoFor(reference).mergeUnsafe(minimumKeepInfoForReference);
+  }
+
+  public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+    MapUtils.removeIf(
+        minimumKeepInfo,
+        (reference, minimumKeepInfoForReference) -> {
+          assert !minimumKeepInfoForReference.isBottom();
+          ProgramDefinition definition =
+              reference.apply(
+                  clazz -> asProgramClassOrNull(definitions.definitionFor(clazz)),
+                  field ->
+                      field.lookupOnProgramClass(
+                          asProgramClassOrNull(definitions.definitionFor(field.getHolderType()))),
+                  method ->
+                      method.lookupOnProgramClass(
+                          asProgramClassOrNull(definitions.definitionFor(method.getHolderType()))));
+          return definition == null || !enqueuer.isReachable(definition);
+        });
+  }
+
+  public KeepClassInfo.Joiner remove(DexType clazz) {
+    return (KeepClassInfo.Joiner) minimumKeepInfo.remove(clazz);
+  }
+
+  public KeepFieldInfo.Joiner remove(DexField field) {
+    return (KeepFieldInfo.Joiner) minimumKeepInfo.remove(field);
+  }
+
+  public KeepMethodInfo.Joiner remove(DexMethod method) {
+    return (KeepMethodInfo.Joiner) minimumKeepInfo.remove(method);
+  }
+
+  public MinimumKeepInfoCollection rewrittenWithLens(GraphLens graphLens) {
+    MinimumKeepInfoCollection rewrittenMinimumKeepInfo = new MinimumKeepInfoCollection();
+    forEach(
+        (reference, minimumKeepInfoForReference) -> {
+          DexReference rewrittenReference =
+              reference.apply(
+                  type -> {
+                    DexType rewrittenType = graphLens.lookupType(type);
+                    if (rewrittenType.isPrimitiveType()) {
+                      // May happen due to enum unboxing.
+                      assert type.isClassType();
+                      assert rewrittenType.isIntType();
+                      return null;
+                    }
+                    return rewrittenType;
+                  },
+                  graphLens::getRenamedFieldSignature,
+                  graphLens::getRenamedMethodSignature);
+          if (rewrittenReference != null) {
+            rewrittenMinimumKeepInfo
+                .getOrCreateMinimumKeepInfoFor(rewrittenReference)
+                .mergeUnsafe(minimumKeepInfoForReference);
+          }
+        });
+    return rewrittenMinimumKeepInfo;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 948caf2..c585b2d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -124,6 +124,10 @@
       return this;
     }
 
+    boolean isAccessModificationEnabled() {
+      return allowAccessModification;
+    }
+
     boolean isObfuscating() {
       return obfuscating;
     }
@@ -380,14 +384,15 @@
       }
       // If either of the flags -dontshrink or -dontobfuscate, or shrinking or minification is
       // turned off through the API, then add a match all rule which will apply that.
-      if (!isShrinking() || !isObfuscating()) {
+      if (!isObfuscating() || !isOptimizing() || !isShrinking()) {
         ProguardKeepRule rule =
             ProguardKeepRule.defaultKeepAllRule(
                 modifiers -> {
-                  modifiers.setAllowsShrinking(isShrinking());
-                  // TODO(b/189807246): This should be removed.
-                  modifiers.setAllowsOptimization(true);
-                  modifiers.setAllowsObfuscation(isObfuscating());
+                  modifiers
+                      .setAllowsAccessModification(isAccessModificationEnabled())
+                      .setAllowsObfuscation(isObfuscating())
+                      .setAllowsOptimization(isOptimizing())
+                      .setAllowsShrinking(isShrinking());
 
                   // In non-compatibility mode, adding -dontoptimize does not cause all annotations
                   // to be retained.
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index 3021c88..7535a3e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -25,12 +25,14 @@
       return this;
     }
 
-    public void setAllowsShrinking(boolean allowsShrinking) {
+    public Builder setAllowsShrinking(boolean allowsShrinking) {
       this.allowsShrinking = allowsShrinking;
+      return this;
     }
 
-    public void setAllowsOptimization(boolean allowsOptimization) {
+    public Builder setAllowsOptimization(boolean allowsOptimization) {
       this.allowsOptimization = allowsOptimization;
+      return this;
     }
 
     public Builder setAllowsObfuscation(boolean allowsObfuscation) {
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 03e9de3..73f5c94 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType;
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 import static java.util.Collections.emptyMap;
 
 import com.android.tools.r8.dex.Constants;
@@ -36,12 +35,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens;
+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.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -53,16 +51,13 @@
 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.Consumer3;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OriginWithPosition;
 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.TriConsumer;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -91,41 +86,20 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
-import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 public class RootSetUtils {
 
-  static void modifyDependentMinimumKeepInfo(
-      Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
-      EnqueuerEvent event,
-      ProgramDefinition dependent,
-      Consumer<Joiner<?, ?, ?>> consumer) {
-    Joiner<?, ?, ?> joiner =
-        dependentMinimumKeepInfo
-            .computeIfAbsent(event, ignoreKey(IdentityHashMap::new))
-            .computeIfAbsent(
-                dependent.getReference(),
-                key ->
-                    key.apply(
-                        clazz -> KeepClassInfo.newEmptyJoiner(),
-                        field -> KeepFieldInfo.newEmptyJoiner(),
-                        method -> KeepMethodInfo.newEmptyJoiner()));
-    consumer.accept(joiner);
-    assert !joiner.isBottom();
-  }
-
   public static class RootSetBuilder {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
     private final SubtypingInfo subtypingInfo;
     private final DirectMappedDexApplication application;
     private final Iterable<? extends ProguardConfigurationRule> rules;
-    private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
-    private final Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>>
-        dependentMinimumKeepInfo = new HashMap<>();
+    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();
@@ -144,8 +118,6 @@
     private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
     private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
     private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
-    private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
-        new IdentityHashMap<>();
     private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
         new IdentityHashMap<>();
     private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
@@ -386,7 +358,6 @@
               && Sets.intersection(neverInline, forceInline).isEmpty()
           : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
       return new RootSet(
-          noShrinking,
           dependentMinimumKeepInfo,
           ImmutableList.copyOf(reasonAsked.values()),
           ImmutableList.copyOf(checkDiscarded.values()),
@@ -409,7 +380,6 @@
           mayHaveSideEffects,
           noSideEffects,
           assumedValues,
-          dependentNoShrinking,
           dependentKeepClassCompatRule,
           identifierNameStrings,
           ifRules,
@@ -481,9 +451,7 @@
           neverInline,
           neverInlineDueToSingleCaller,
           neverClassInline,
-          noShrinking,
           dependentMinimumKeepInfo,
-          dependentNoShrinking,
           dependentKeepClassCompatRule,
           Lists.newArrayList(delayedRootSetActionItems));
     }
@@ -769,6 +737,7 @@
     // TODO(b/67934426): Test this code.
     public static void writeSeeds(
         AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
+      InternalOptions options = appInfo.app().options;
       appInfo
           .getKeepInfo()
           .forEachPinnedType(
@@ -776,7 +745,8 @@
                 if (include.test(type)) {
                   out.println(type.toSourceString());
                 }
-              });
+              },
+              options);
       appInfo
           .getKeepInfo()
           .forEachPinnedField(
@@ -789,7 +759,8 @@
                           + " "
                           + field.name.toSourceString());
                 }
-              });
+              },
+              options);
       appInfo
           .getKeepInfo()
           .forEachPinnedMethod(
@@ -826,7 +797,8 @@
                   out.print(param.toSourceString());
                 }
                 out.println(")");
-              });
+              },
+              options);
       out.close();
     }
 
@@ -1105,12 +1077,8 @@
       addItemToSets(clazz, rule, null, null, ifRule);
     }
 
-    // TODO(b/192636793): This needs to use the precondition.
     private void includeDescriptor(
-        ProgramDefinition item,
-        DexType type,
-        ProguardKeepRuleBase context,
-        DexProgramClass precondition) {
+        DexType type, ProguardKeepRuleBase rule, EnqueuerEvent preconditionEvent) {
       if (type.isVoidType()) {
         return;
       }
@@ -1126,31 +1094,33 @@
       }
 
       // Keep the type if the item is also kept.
-      dependentNoShrinking
-          .computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
-          .addClassWithRule(type, context);
+      ProguardKeepRuleModifiers modifiers = rule.getModifiers();
+      if (appView.options().isShrinking() && !modifiers.allowsShrinking) {
+        dependentMinimumKeepInfo
+            .getOrCreateMinimumKeepInfoFor(preconditionEvent, clazz.getReference())
+            .addRule(rule)
+            .disallowShrinking();
+      }
 
       // Disable minification for the type.
-      if (appView.options().isMinificationEnabled()) {
-        modifyDependentMinimumKeepInfo(
-            dependentMinimumKeepInfo,
-            UnconditionalKeepInfoEvent.get(),
-            clazz,
-            Joiner::disallowMinification);
+      if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
+        dependentMinimumKeepInfo
+            .getOrCreateMinimumKeepInfoFor(preconditionEvent, clazz.getReference())
+            .disallowMinification();
       }
     }
 
     private void includeDescriptorClasses(
-        ProgramDefinition item, ProguardKeepRuleBase context, DexProgramClass precondition) {
+        ProgramDefinition item, ProguardKeepRuleBase rule, EnqueuerEvent preconditionEvent) {
       if (item.isMethod()) {
         ProgramMethod method = item.asProgramMethod();
-        includeDescriptor(method, method.getReturnType(), context, precondition);
+        includeDescriptor(method.getReturnType(), rule, preconditionEvent);
         for (DexType value : method.getParameters()) {
-          includeDescriptor(method, value, context, precondition);
+          includeDescriptor(value, rule, preconditionEvent);
         }
       } else if (item.isField()) {
         ProgramField field = item.asProgramField();
-        includeDescriptor(field, field.getType(), context, precondition);
+        includeDescriptor(field.getType(), rule, preconditionEvent);
       } else {
         assert item.isClass();
       }
@@ -1162,12 +1132,13 @@
         ProguardMemberRule rule,
         DexProgramClass precondition,
         ProguardIfRule ifRule) {
-      if (context instanceof ProguardKeepRule) {
+      if (context.isProguardKeepRule()) {
         if (!item.isProgramDefinition()) {
           // Keep rules do not apply to non-program items.
           return;
         }
-        evaluateKeepRule(item.asProgramDefinition(), context, rule, precondition, ifRule);
+        evaluateKeepRule(
+            item.asProgramDefinition(), context.asProguardKeepRule(), rule, precondition, ifRule);
       } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
         mayHaveSideEffects.put(item.getReference(), rule);
         context.markAsUsed();
@@ -1335,7 +1306,7 @@
 
     private synchronized void evaluateKeepRule(
         ProgramDefinition item,
-        ProguardConfigurationRule context,
+        ProguardKeepRule context,
         ProguardMemberRule rule,
         DexProgramClass precondition,
         ProguardIfRule ifRule) {
@@ -1384,10 +1355,10 @@
       }
 
       // The reason for keeping should link to the conditional rule as a whole, if present.
-      ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
+      ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : context;
 
       // The modifiers are specified on the actual keep rule (ie, the consequent/context).
-      ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
+      ProguardKeepRuleModifiers modifiers = context.getModifiers();
       if (modifiers.isBottom()) {
         // This rule is a no-op.
         return;
@@ -1406,56 +1377,61 @@
         }
       }
 
-      // TODO(b/192636793): Remove the noShrinking and dependentNoShrinking collections. A
-      //  prerequisite for this is that the ProguardKeepRule instances are added to the KeepInfo,
-      //  since this is needed for the -whyareyoukeeping graph.
-      if (!modifiers.allowsShrinking) {
-        if (precondition != null) {
-          dependentNoShrinking
-              .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
-              .addReferenceWithRule(item.getReference(), keepRule);
-        } else {
-          noShrinking.addReferenceWithRule(item.getReference(), keepRule);
-        }
-        context.markAsUsed();
-      }
-
       EnqueuerEvent preconditionEvent;
       if (precondition != null) {
         preconditionEvent =
             item.getAccessFlags().isStatic()
+                    || (item.isMethod() && item.asMethod().getDefinition().isInstanceInitializer())
                 ? new LiveClassEnqueuerEvent(precondition)
                 : new InstantiatedClassEnqueuerEvent(precondition);
       } else {
         preconditionEvent = UnconditionalKeepInfoEvent.get();
       }
 
+      if (appView.options().isAccessModificationEnabled() && !modifiers.allowsAccessModification) {
+        dependentMinimumKeepInfo
+            .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
+            .disallowAccessModification();
+        context.markAsUsed();
+      }
+
       if (appView.options().isAnnotationRemovalEnabled() && !modifiers.allowsAnnotationRemoval) {
-        modifyDependentMinimumKeepInfo(
-            dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowAnnotationRemoval);
+        dependentMinimumKeepInfo
+            .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
+            .disallowAnnotationRemoval();
         context.markAsUsed();
       }
 
       if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
-        modifyDependentMinimumKeepInfo(
-            dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowMinification);
+        dependentMinimumKeepInfo
+            .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
+            .disallowMinification();
         context.markAsUsed();
       }
 
       if (appView.options().isOptimizationEnabled() && !modifiers.allowsOptimization) {
-        modifyDependentMinimumKeepInfo(
-            dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowOptimization);
+        dependentMinimumKeepInfo
+            .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
+            .disallowOptimization();
         context.markAsUsed();
       }
 
-      if (appView.options().isShrinking() && !modifiers.allowsShrinking) {
-        modifyDependentMinimumKeepInfo(
-            dependentMinimumKeepInfo, preconditionEvent, item, Joiner::disallowShrinking);
+      if ((appView.options().isShrinking() || isMainDexRootSetBuilder())
+          && !modifiers.allowsShrinking) {
+        KeepInfo.Joiner<?, ?, ?> minimumKeepInfoForItem =
+            dependentMinimumKeepInfo
+                .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
+                .addRule(keepRule)
+                .disallowShrinking();
         context.markAsUsed();
+
+        if (item.getAccessFlags().isPackagePrivateOrProtected()) {
+          minimumKeepInfoForItem.requireAccessModificationForRepackaging();
+        }
       }
 
       if (modifiers.includeDescriptorClasses) {
-        includeDescriptorClasses(item, keepRule, precondition);
+        includeDescriptorClasses(item, keepRule, preconditionEvent);
         context.markAsUsed();
       }
     }
@@ -1507,9 +1483,7 @@
     final Set<DexMethod> neverInline;
     final Set<DexMethod> neverInlineDueToSingleCaller;
     final Set<DexType> neverClassInline;
-    final MutableItemsWithRules noShrinking;
-    final Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo;
-    final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
+    private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
     final List<DelayedRootSetActionItem> delayedRootSetActionItems;
 
@@ -1517,433 +1491,23 @@
         Set<DexMethod> neverInline,
         Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
-        MutableItemsWithRules noShrinking,
-        Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
-        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+        DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.neverInline = neverInline;
       this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
       this.neverClassInline = neverClassInline;
-      this.noShrinking = noShrinking;
       this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
-      this.dependentNoShrinking = dependentNoShrinking;
       this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
       this.delayedRootSetActionItems = delayedRootSetActionItems;
     }
 
-    public void forEachClassWithDependentItems(
-        DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
-      for (DexReference reference : dependentNoShrinking.keySet()) {
-        if (reference.isDexType()) {
-          DexType type = reference.asDexType();
-          DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
-          if (clazz != null) {
-            consumer.accept(clazz);
-          }
-        }
-      }
-    }
-
-    public void forEachMemberWithDependentItems(
-        DexDefinitionSupplier definitions,
-        BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
-      dependentNoShrinking.forEach(
-          (reference, dependentItems) -> {
-            if (reference.isDexMember()) {
-              DexMember<?, ?> member = reference.asDexMember();
-              DexProgramClass holder =
-                  asProgramClassOrNull(definitions.definitionForHolder(member));
-              if (holder != null) {
-                DexEncodedMember<?, ?> definition = holder.lookupMember(member);
-                if (definition != null) {
-                  consumer.accept(definition, dependentItems);
-                }
-              }
-            }
-          });
-    }
-
-    public void forEachDependentInstanceConstructor(
-        DexProgramClass clazz,
-        AppView<?> appView,
-        BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
-      getDependentItems(clazz)
-          .forEachMethod(
-              (reference, reasons) -> {
-                DexProgramClass holder =
-                    asProgramClassOrNull(appView.definitionForHolder(reference));
-                if (holder != null) {
-                  ProgramMethod method = holder.lookupProgramMethod(reference);
-                  if (method != null && method.getDefinition().isInstanceInitializer()) {
-                    fn.accept(method, reasons);
-                  }
-                }
-              });
-    }
-
-    public void forEachDependentMember(
-        DexDefinition item,
-        AppView<?> appView,
-        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
-      getDependentItems(item)
-          .forEachMember(
-              (reference, reasons) -> {
-                DexProgramClass holder =
-                    asProgramClassOrNull(appView.definitionForHolder(reference));
-                if (holder != null) {
-                  ProgramMember<?, ?> member = holder.lookupProgramMember(reference);
-                  if (member != null) {
-                    fn.accept(item, member, reasons);
-                  }
-                }
-              });
-    }
-
-    public void forEachDependentNonStaticMember(
-        DexDefinition item,
-        AppView<?> appView,
-        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
-      forEachDependentMember(
-          item,
-          appView,
-          (precondition, member, reasons) -> {
-            if (!member.getDefinition().isStatic()) {
-              fn.accept(precondition, member, reasons);
-            }
-          });
-    }
-
-    public void forEachDependentStaticMember(
-        DexDefinition item,
-        AppView<?> appView,
-        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
-      forEachDependentMember(
-          item,
-          appView,
-          (precondition, member, reasons) -> {
-            if (member.getDefinition().isStatic()) {
-              fn.accept(precondition, member, reasons);
-            }
-          });
-    }
-
-    ItemsWithRules getDependentItems(DexDefinition item) {
-      ItemsWithRules found = dependentNoShrinking.get(item.getReference());
-      return found != null ? found : ItemsWithRules.empty();
-    }
-
     Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
       return dependentKeepClassCompatRule.get(type);
     }
 
-    public void forEachMinimumKeepInfo(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
-        TriConsumer<EnqueuerEvent, DexProgramClass, KeepClassInfo.Joiner> classConsumer,
-        TriConsumer<EnqueuerEvent, ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
-        TriConsumer<EnqueuerEvent, ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
-      dependentMinimumKeepInfo.forEach(
-          (precondition, minimumKeepInfoForDependents) ->
-              internalForEachMinimumKeepInfo(
-                  appView,
-                  minimumKeepInfoForDependents,
-                  precondition,
-                  classConsumer,
-                  fieldConsumer,
-                  methodConsumer));
-    }
-
-    private static void internalForEachMinimumKeepInfo(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
-        Map<DexReference, KeepInfo.Joiner<?, ?, ?>> minimumKeepInfo,
-        EnqueuerEvent precondition,
-        TriConsumer<EnqueuerEvent, DexProgramClass, KeepClassInfo.Joiner> classConsumer,
-        TriConsumer<EnqueuerEvent, ProgramField, KeepFieldInfo.Joiner> fieldConsumer,
-        TriConsumer<EnqueuerEvent, ProgramMethod, KeepMethodInfo.Joiner> methodConsumer) {
-      minimumKeepInfo.forEach(
-          (reference, joiner) -> {
-            DexProgramClass contextClass =
-                asProgramClassOrNull(appView.definitionFor(reference.getContextType()));
-            if (contextClass != null) {
-              reference.accept(
-                  clazz -> classConsumer.accept(precondition, contextClass, joiner.asClassJoiner()),
-                  fieldReference -> {
-                    ProgramField field = contextClass.lookupProgramField(fieldReference);
-                    if (field != null) {
-                      fieldConsumer.accept(precondition, field, joiner.asFieldJoiner());
-                    }
-                  },
-                  methodReference -> {
-                    ProgramMethod method = contextClass.lookupProgramMethod(methodReference);
-                    if (method != null) {
-                      methodConsumer.accept(precondition, method, joiner.asMethodJoiner());
-                    }
-                  });
-            }
-          });
-    }
-  }
-
-  abstract static class ItemsWithRules {
-
-    public static ItemsWithRules empty() {
-      return MutableItemsWithRules.EMPTY;
-    }
-
-    public abstract boolean containsClass(DexType type);
-
-    public abstract boolean containsField(DexField field);
-
-    public abstract boolean containsMethod(DexMethod method);
-
-    public final boolean containsReference(DexReference reference) {
-      return reference.apply(this::containsClass, this::containsField, this::containsMethod);
-    }
-
-    public abstract void forEachClass(Consumer<? super DexType> consumer);
-
-    public abstract void forEachClass(
-        BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract void forEachField(Consumer<? super DexField> consumer);
-
-    public abstract void forEachField(
-        BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer);
-
-    public abstract void forEachMember(
-        BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
-
-    public abstract void forEachMethod(
-        BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
-
-    public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
-
-    public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
-
-    public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
-      return reference.apply(
-          this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
-    }
-  }
-
-  static class MutableItemsWithRules extends ItemsWithRules {
-
-    private static final ItemsWithRules EMPTY =
-        new MutableItemsWithRules(emptyMap(), emptyMap(), emptyMap());
-
-    final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
-    final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
-    final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
-
-    MutableItemsWithRules() {
-      this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
-    }
-
-    private MutableItemsWithRules(
-        Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
-        Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
-        Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
-      this.classesWithRules = classesWithRules;
-      this.fieldsWithRules = fieldsWithRules;
-      this.methodsWithRules = methodsWithRules;
-    }
-
-    public void addAll(ItemsWithRules items) {
-      items.forEachClass(this::addClassWithRules);
-      items.forEachField(this::addFieldWithRules);
-      items.forEachMethod(this::addMethodWithRules);
-    }
-
-    public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
-      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
-    }
-
-    public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
-      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
-    }
-
-    public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
-      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
-    }
-
-    public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
-      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
-    }
-
-    public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
-      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
-    }
-
-    public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
-      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
-    }
-
-    public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
-      reference.accept(
-          this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
-    }
-
-    public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
-      reference.accept(
-          this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
-    }
-
-    @Override
-    public boolean containsClass(DexType type) {
-      return classesWithRules.containsKey(type);
-    }
-
-    @Override
-    public boolean containsField(DexField field) {
-      return fieldsWithRules.containsKey(field);
-    }
-
-    @Override
-    public boolean containsMethod(DexMethod method) {
-      return methodsWithRules.containsKey(method);
-    }
-
-    public void forEachReference(Consumer<DexReference> consumer) {
-      forEachClass(consumer);
-      forEachMember(consumer);
-    }
-
-    @Override
-    public void forEachClass(Consumer<? super DexType> consumer) {
-      classesWithRules.keySet().forEach(consumer);
-    }
-
-    @Override
-    public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) {
-      classesWithRules.forEach(consumer);
-    }
-
-    @Override
-    public void forEachField(Consumer<? super DexField> consumer) {
-      fieldsWithRules.keySet().forEach(consumer);
-    }
-
-    @Override
-    public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
-      fieldsWithRules.forEach(consumer);
-    }
-
-    @Override
-    public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) {
-      forEachField(consumer);
-      forEachMethod(consumer);
-    }
-
-    @Override
-    public void forEachMember(
-        BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
-      forEachField(consumer);
-      forEachMethod(consumer);
-    }
-
-    @Override
-    public void forEachMethod(Consumer<? super DexMethod> consumer) {
-      methodsWithRules.keySet().forEach(consumer);
-    }
-
-    @Override
-    public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
-      methodsWithRules.forEach(consumer);
-    }
-
-    @Override
-    public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
-      return classesWithRules.get(type);
-    }
-
-    @Override
-    public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
-      return fieldsWithRules.get(field);
-    }
-
-    @Override
-    public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
-      return methodsWithRules.get(method);
-    }
-
-    public void removeClass(DexType type) {
-      classesWithRules.remove(type);
-    }
-
-    public void removeField(DexField field) {
-      fieldsWithRules.remove(field);
-    }
-
-    public void removeMethod(DexMethod method) {
-      methodsWithRules.remove(method);
-    }
-
-    public void removeReference(DexReference reference) {
-      reference.accept(this::removeClass, this::removeField, this::removeMethod);
-    }
-
-    public void putAll(ItemsWithRules items) {
-      items.forEachClass(this::putClassWithRules);
-      items.forEachField(this::putFieldWithRules);
-      items.forEachMethod(this::putMethodWithRules);
-    }
-
-    public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
-      classesWithRules.put(type, rules);
-    }
-
-    public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
-      fieldsWithRules.put(field, rules);
-    }
-
-    public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
-      methodsWithRules.put(method, rules);
-    }
-
-    public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
-      reference.accept(
-          this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
-    }
-
-    public int size() {
-      return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
-    }
-
-    public void forEachReference(
-        BiConsumer<? super DexReference, Set<ProguardKeepRuleBase>> consumer) {
-      forEachClass(consumer);
-      forEachMember(consumer);
-    }
-
-    private MutableItemsWithRules prune(Set<DexType> prunedClasses) {
-      MutableItemsWithRules prunedItemsWithRules = new MutableItemsWithRules();
-      forEachReference(
-          (reference, rules) -> {
-            if (!prunedClasses.contains(reference.getContextType())) {
-              prunedItemsWithRules.addReferenceWithRules(reference, rules);
-            }
-          });
-      return prunedItemsWithRules;
-    }
-
-    private MutableItemsWithRules rewrittenWithLens(GraphLens graphLens) {
-      if (graphLens.isIdentityLens()) {
-        return this;
-      }
-      MutableItemsWithRules rewrittenItemsWithRules = new MutableItemsWithRules();
-      forEachReference(
-          (reference, rules) ->
-              rewriteAndApplyIfNotPrimitiveType(
-                  graphLens,
-                  reference,
-                  rewritten -> rewrittenItemsWithRules.addReferenceWithRules(rewritten, rules)));
-      return rewrittenItemsWithRules;
+    public DependentMinimumKeepInfoCollection getDependentMinimumKeepInfo() {
+      return dependentMinimumKeepInfo;
     }
   }
 
@@ -1971,8 +1535,7 @@
     public final Set<ProguardIfRule> ifRules;
 
     private RootSet(
-        MutableItemsWithRules noShrinking,
-        Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
+        DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         ImmutableList<DexReference> reasonAsked,
         ImmutableList<DexReference> checkDiscarded,
         Set<DexMethod> alwaysInline,
@@ -1994,7 +1557,6 @@
         Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
         Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
         Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
-        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         Set<DexReference> identifierNameStrings,
         Set<ProguardIfRule> ifRules,
@@ -2003,9 +1565,7 @@
           neverInline,
           neverInlineDueToSingleCaller,
           neverClassInline,
-          noShrinking,
           dependentMinimumKeepInfo,
-          dependentNoShrinking,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems);
       this.reasonAsked = reasonAsked;
@@ -2046,14 +1606,10 @@
       }
     }
 
-    void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
+    void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
       neverInline.addAll(consequentRootSet.neverInline);
       neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
       neverClassInline.addAll(consequentRootSet.neverClassInline);
-      if (addNoShrinking) {
-        noShrinking.addAll(consequentRootSet.noShrinking);
-      }
-      addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
       consequentRootSet.dependentKeepClassCompatRule.forEach(
           (type, rules) ->
               dependentKeepClassCompatRule
@@ -2062,18 +1618,20 @@
       delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
     }
 
-    // Add dependent items that depend on -if rules.
-    static void addDependentItems(
-        Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd,
-        Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) {
-      dependentItemsToAdd.forEach(
-          (reference, dependence) ->
-              dependentItemsToAddTo
-                  .computeIfAbsent(reference, x -> new MutableItemsWithRules())
-                  .putAll(dependence));
+    public boolean isShrinkingDisallowedUnconditionally(
+        ProgramDefinition definition, InternalOptions options) {
+      if (!options.isShrinking()) {
+        return true;
+      }
+      return getDependentMinimumKeepInfo()
+          .getOrDefault(UnconditionalKeepInfoEvent.get(), MinimumKeepInfoCollection.empty())
+          .hasMinimumKeepInfoThatMatches(
+              definition.getReference(),
+              minimumKeepInfoForDefinition -> !minimumKeepInfoForDefinition.isShrinkingAllowed());
     }
 
     public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+      getDependentMinimumKeepInfo().pruneDeadItems(definitions, enqueuer);
       pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
       pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
       pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
@@ -2087,94 +1645,96 @@
         Enqueuer enqueuer) {
       references.removeIf(
           reference -> {
-            if (reference.isDexType()) {
-              DexClass definition = definitions.definitionFor(reference.asDexType());
-              return definition == null || !enqueuer.isTypeLive(definition);
-            }
-
-            assert reference.isDexMember();
-
-            DexMember<?, ?> member = reference.asDexMember();
-            DexClass holder = definitions.definitionForHolder(member);
-            DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
-            if (definition == null) {
-              return true;
-            }
-            if (holder.isProgramClass()) {
-              if (definition.isDexEncodedField()) {
-                DexEncodedField field = definition.asDexEncodedField();
-                return !enqueuer.isFieldReferenced(field);
-              }
-              assert definition.isDexEncodedMethod();
-              DexEncodedMethod method = definition.asDexEncodedMethod();
-              return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
-            }
-            return !enqueuer.isNonProgramTypeLive(holder);
+            Definition definition =
+                reference.apply(
+                    definitions::definitionFor,
+                    field ->
+                        field.lookupMemberOnClass(definitions.definitionFor(field.getHolderType())),
+                    method ->
+                        method.lookupMemberOnClass(
+                            definitions.definitionFor(method.getHolderType())));
+            return definition == null || !enqueuer.isReachable(definition);
           });
     }
 
     void shouldNotBeMinified(ProgramDefinition definition) {
-      modifyDependentMinimumKeepInfo(
-          dependentMinimumKeepInfo,
-          UnconditionalKeepInfoEvent.get(),
-          definition,
-          Joiner::disallowMinification);
+      getDependentMinimumKeepInfo()
+          .getOrCreateUnconditionalMinimumKeepInfoFor(definition.getReference())
+          .disallowMinification();
     }
 
-    public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
-      noShrinking.forEachField(
-          reference -> {
-            DexClass holder = appInfo.definitionForHolder(reference);
-            DexEncodedField field = reference.lookupOnClass(holder);
-            if (field != null
-                && (field.isStatic()
-                    || isKeptDirectlyOrIndirectly(field.getHolderType(), appInfo))) {
-              assert appInfo.isFieldRead(field)
-                  : "Expected kept field `" + field.toSourceString() + "` to be read";
-              assert appInfo.isFieldWritten(field)
-                  : "Expected kept field `" + field.toSourceString() + "` to be written";
-            }
-          });
+    public boolean verifyKeptFieldsAreAccessedAndLive(AppView<AppInfoWithLiveness> appView) {
+      getDependentMinimumKeepInfo()
+          .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty())
+          .forEachThatMatches(
+              (reference, minimumKeepInfo) ->
+                  reference.isDexField() && !minimumKeepInfo.isShrinkingAllowed(),
+              (reference, minimumKeepInfo) -> {
+                DexField fieldReference = reference.asDexField();
+                DexProgramClass holder =
+                    asProgramClassOrNull(appView.definitionForHolder(fieldReference));
+                ProgramField field = fieldReference.lookupOnProgramClass(holder);
+                if (field != null
+                    && (field.getAccessFlags().isStatic()
+                        || isKeptDirectlyOrIndirectly(field.getHolderType(), appView))) {
+                  assert appView.appInfo().isFieldRead(field.getDefinition())
+                      : "Expected kept field `" + fieldReference.toSourceString() + "` to be read";
+                  assert appView.appInfo().isFieldWritten(field.getDefinition())
+                      : "Expected kept field `"
+                          + fieldReference.toSourceString()
+                          + "` to be written";
+                }
+              });
       return true;
     }
 
-    public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
-      noShrinking.forEachMethod(
-          reference -> {
-            assert appInfo.isTargetedMethod(reference)
-                : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
-            DexEncodedMethod method =
-                appInfo.definitionForHolder(reference).lookupMethod(reference);
-            if (!method.isAbstract()
-                && isKeptDirectlyOrIndirectly(method.getHolderType(), appInfo)) {
-              assert appInfo.isLiveMethod(reference)
-                  : "Expected non-abstract kept method `"
-                      + reference.toSourceString()
-                      + "` to be live";
-            }
-          });
+    public boolean verifyKeptMethodsAreTargetedAndLive(AppView<AppInfoWithLiveness> appView) {
+      getDependentMinimumKeepInfo()
+          .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty())
+          .forEachThatMatches(
+              (reference, minimumKeepInfo) ->
+                  reference.isDexMethod() && !minimumKeepInfo.isShrinkingAllowed(),
+              (reference, minimumKeepInfo) -> {
+                DexMethod methodReference = reference.asDexMethod();
+                assert appView.appInfo().isTargetedMethod(methodReference)
+                    : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
+                DexEncodedMethod method =
+                    appView.definitionForHolder(methodReference).lookupMethod(methodReference);
+                if (!method.isAbstract()
+                    && isKeptDirectlyOrIndirectly(methodReference.getHolderType(), appView)) {
+                  assert appView.appInfo().isLiveMethod(methodReference)
+                      : "Expected non-abstract kept method `"
+                          + reference.toSourceString()
+                          + "` to be live";
+                }
+              });
       return true;
     }
 
-    public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
-      noShrinking.forEachClass(
-          type -> {
-            assert appInfo.isLiveProgramType(type)
-                : "Expected kept type `" + type.toSourceString() + "` to be live";
-          });
+    public boolean verifyKeptTypesAreLive(AppView<AppInfoWithLiveness> appView) {
+      getDependentMinimumKeepInfo()
+          .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty())
+          .forEachThatMatches(
+              (reference, minimumKeepInfo) ->
+                  reference.isDexType() && !minimumKeepInfo.isShrinkingAllowed(),
+              (reference, minimumKeepInfo) -> {
+                DexType type = reference.asDexType();
+                assert appView.appInfo().isLiveProgramType(type)
+                    : "Expected kept type `" + type.toSourceString() + "` to be live";
+              });
       return true;
     }
 
-    private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
-      if (noShrinking.containsClass(type)) {
-        return true;
-      }
-      DexClass clazz = appInfo.definitionFor(type);
+    private boolean isKeptDirectlyOrIndirectly(DexType type, AppView<AppInfoWithLiveness> appView) {
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
       if (clazz == null) {
         return false;
       }
+      if (isShrinkingDisallowedUnconditionally(clazz, appView.options())) {
+        return true;
+      }
       if (clazz.superType != null) {
-        return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
+        return isKeptDirectlyOrIndirectly(clazz.superType, appView);
       }
       return false;
     }
@@ -2184,22 +1744,30 @@
       GraphLens lens = appView.graphLens();
       // Create a mapping from each required type to the set of required members on that type.
       Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
-      noShrinking.forEachClass(
-          type -> {
-            DexType rewrittenType = lens.lookupType(type);
-            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType)
-                : "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned";
-            requiredMembersPerType.computeIfAbsent(rewrittenType, key -> Sets.newIdentityHashSet());
-          });
-      noShrinking.forEachMember(
-          member -> {
-            DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member);
-            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember)
-                : "Expected reference `" + rewrittenMember.toSourceString() + "` to be pinned";
-            requiredMembersPerType
-                .computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet())
-                .add(rewrittenMember);
-          });
+      getDependentMinimumKeepInfo()
+          .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty())
+          .forEachThatMatches(
+              (reference, minimumKeepInfo) -> !minimumKeepInfo.isShrinkingAllowed(),
+              (reference, minimumKeepInfo) -> {
+                if (reference.isDexType()) {
+                  DexType type = reference.asDexType();
+                  DexType rewrittenType = lens.lookupType(type);
+                  assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType)
+                      : "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned";
+                  requiredMembersPerType.computeIfAbsent(
+                      rewrittenType, key -> Sets.newIdentityHashSet());
+                } else {
+                  DexMember<?, ?> member = reference.asDexMember();
+                  DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member);
+                  assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember)
+                      : "Expected reference `"
+                          + rewrittenMember.toSourceString()
+                          + "` to be pinned";
+                  requiredMembersPerType
+                      .computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet())
+                      .add(rewrittenMember);
+                }
+              });
 
       // Run through each class in the program and check that it has members it must have.
       for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -2258,12 +1826,10 @@
     public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("RootSet");
-      builder.append("\nnoShrinking: " + noShrinking.size());
       builder.append("\nreasonAsked: " + reasonAsked.size());
       builder.append("\ncheckDiscarded: " + checkDiscarded.size());
       builder.append("\nnoSideEffects: " + noSideEffects.size());
       builder.append("\nassumedValues: " + assumedValues.size());
-      builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
       builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
       builder.append("\nifRules: " + ifRules.size());
       return builder.toString();
@@ -2312,18 +1878,14 @@
         Set<DexMethod> neverInline,
         Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
-        MutableItemsWithRules noShrinking,
-        Map<EnqueuerEvent, Map<DexReference, KeepInfo.Joiner<?, ?, ?>>> dependentMinimumKeepInfo,
-        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+        DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
           neverInline,
           neverInlineDueToSingleCaller,
           neverClassInline,
-          noShrinking,
           dependentMinimumKeepInfo,
-          dependentNoShrinking,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems);
     }
@@ -2355,10 +1917,9 @@
       // Call the super builder to have if-tests calculated automatically.
       RootSet rootSet = super.build(executorService);
       return new MainDexRootSet(
-          rootSet.noShrinking,
+          rootSet.getDependentMinimumKeepInfo(),
           rootSet.reasonAsked,
           rootSet.checkDiscarded,
-          rootSet.dependentNoShrinking,
           rootSet.ifRules,
           rootSet.delayedRootSetActionItems);
     }
@@ -2367,15 +1928,13 @@
   public static class MainDexRootSet extends RootSet {
 
     public MainDexRootSet(
-        MutableItemsWithRules noShrinking,
+        DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         ImmutableList<DexReference> reasonAsked,
         ImmutableList<DexReference> checkDiscarded,
-        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
         Set<ProguardIfRule> ifRules,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
-          noShrinking,
-          emptyMap(),
+          dependentMinimumKeepInfo,
           reasonAsked,
           checkDiscarded,
           Collections.emptySet(),
@@ -2397,26 +1956,12 @@
           emptyMap(),
           emptyMap(),
           emptyMap(),
-          dependentNoShrinking,
           emptyMap(),
           Collections.emptySet(),
           ifRules,
           delayedRootSetActionItems);
     }
 
-    @Override
-    void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
-      if (addNoShrinking) {
-        noShrinking.addAll(consequentRootSet.noShrinking);
-      }
-      addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
-      consequentRootSet.dependentKeepClassCompatRule.forEach(
-          (type, rules) ->
-              dependentKeepClassCompatRule
-                  .computeIfAbsent(type, k -> new HashSet<>())
-                  .addAll(rules));
-    }
-
     public static MainDexRootSetBuilder builder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         SubtypingInfo subtypingInfo,
@@ -2433,21 +1978,6 @@
       if (graphLens.isIdentityLens()) {
         return this;
       }
-      Map<DexReference, MutableItemsWithRules> rewrittenDependent = new IdentityHashMap<>();
-      dependentNoShrinking.forEach(
-          (reference, rules) -> {
-            // Rewriting a reference can result in us having to merge items with rules.
-            rewriteAndApplyIfNotPrimitiveType(
-                graphLens,
-                reference,
-                rewritten -> {
-                  MutableItemsWithRules rewrittenRules =
-                      rewrittenDependent.computeIfAbsent(
-                          graphLens.lookupReference(reference),
-                          rewrittenRef -> new MutableItemsWithRules());
-                  rewrittenRules.addAll(rules.rewrittenWithLens(graphLens));
-                });
-          });
 
       ImmutableList.Builder<DexReference> rewrittenCheckDiscarded = ImmutableList.builder();
       checkDiscarded.forEach(
@@ -2464,10 +1994,9 @@
       // All delayed root set actions should have been processed at this point.
       assert delayedRootSetActionItems.isEmpty();
       return new MainDexRootSet(
-          noShrinking.rewrittenWithLens(graphLens),
+          getDependentMinimumKeepInfo().rewrittenWithLens(graphLens),
           rewrittenReasonAsked.build(),
           rewrittenCheckDiscarded.build(),
-          rewrittenDependent,
           ifRules,
           delayedRootSetActionItems);
     }
@@ -2476,25 +2005,15 @@
       if (prunedItems.isEmpty()) {
         return this;
       }
-      Map<DexReference, MutableItemsWithRules> prunedDependent = new IdentityHashMap<>();
-      dependentNoShrinking.forEach(
-          (ref, rules) -> {
-            if (prunedItems.getRemovedClasses().contains(ref.getContextType())) {
-              // The dependent reference has been pruned and cannot lead to any additional items
-              return;
-            }
-            prunedDependent.put(ref, rules.prune(prunedItems.getRemovedClasses()));
-          });
       // TODO(b/164019179): If rules can now reference dead items. These should be pruned or
-      //  rewritten
+      //  rewritten.
       ifRules.forEach(ProguardIfRule::canReferenceDeadTypes);
       // All delayed root set actions should have been processed at this point.
       assert delayedRootSetActionItems.isEmpty();
       return new MainDexRootSet(
-          noShrinking.prune(prunedItems.getRemovedClasses()),
+          getDependentMinimumKeepInfo(),
           reasonAsked,
           checkDiscarded,
-          prunedDependent,
           ifRules,
           delayedRootSetActionItems);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 8bd2f01..0a49379 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -45,10 +45,10 @@
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
@@ -68,6 +68,7 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
@@ -213,6 +214,7 @@
   private final DexApplication application;
   private final AppInfoWithLiveness appInfo;
   private final AppView<AppInfoWithLiveness> appView;
+  private final InternalOptions options;
   private final SubtypingInfo subtypingInfo;
   private final ExecutorService executorService;
   private final MethodPoolCollection methodPoolCollection;
@@ -248,6 +250,7 @@
     this.application = application;
     this.appInfo = appView.appInfo();
     this.appView = appView;
+    this.options = appView.options();
     this.mainDexInfo = appInfo.getMainDexInfo();
     this.subtypingInfo = appInfo.computeSubtypingInfo();
     this.executorService = executorService;
@@ -291,9 +294,9 @@
     // the return type and the parameter types of the method.
     // TODO(b/156715504): Compute referenced-by-pinned in the keep info objects.
     List<DexReference> pinnedItems = new ArrayList<>();
-    appInfo.getKeepInfo().forEachPinnedType(pinnedItems::add);
-    appInfo.getKeepInfo().forEachPinnedMethod(pinnedItems::add);
-    appInfo.getKeepInfo().forEachPinnedField(pinnedItems::add);
+    appInfo.getKeepInfo().forEachPinnedType(pinnedItems::add, options);
+    appInfo.getKeepInfo().forEachPinnedMethod(pinnedItems::add, options);
+    appInfo.getKeepInfo().forEachPinnedField(pinnedItems::add, options);
     extractPinnedItems(pinnedItems, AbortReason.PINNED_SOURCE);
 
     for (DexProgramClass clazz : classes) {
@@ -728,7 +731,7 @@
     // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
     // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
     // pinned, because this rewriting does not affect A.method() in any way.
-    assert graphLens.assertPinnedNotModified(appInfo.getKeepInfo());
+    assert graphLens.assertPinnedNotModified(appInfo.getKeepInfo(), options);
 
     for (DexProgramClass clazz : appInfo.classes()) {
       for (DexEncodedMethod encodedMethod : clazz.methods()) {
@@ -1136,19 +1139,26 @@
           interfaces.isEmpty()
               ? DexTypeList.empty()
               : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
-      // Step 2: replace fields and methods.
+      // Step 2: ensure -if rules cannot target the members that were merged into the target class.
+      directMethods.values().forEach(feedback::markMethodCannotBeKept);
+      virtualMethods.values().forEach(feedback::markMethodCannotBeKept);
+      for (int i = 0; i < source.instanceFields().size(); i++) {
+        feedback.markFieldCannotBeKept(mergedInstanceFields[i]);
+      }
+      for (int i = 0; i < source.staticFields().size(); i++) {
+        feedback.markFieldCannotBeKept(mergedStaticFields[i]);
+      }
+      // Step 3: replace fields and methods.
       target.addDirectMethods(directMethods.values());
       target.addVirtualMethods(virtualMethods.values());
       target.setInstanceFields(mergedInstanceFields);
       target.setStaticFields(mergedStaticFields);
-      target.forEachField(feedback::markFieldCannotBeKept);
-      target.forEachMethod(feedback::markMethodCannotBeKept);
-      // Step 3: Clear the members of the source class since they have now been moved to the target.
+      // Step 4: Clear the members of the source class since they have now been moved to the target.
       source.getMethodCollection().clearDirectMethods();
       source.getMethodCollection().clearVirtualMethods();
       source.clearInstanceFields();
       source.clearStaticFields();
-      // Step 4: Record merging.
+      // Step 5: Record merging.
       mergedClasses.put(source.type, target.type);
       assert !abortMerge;
       assert GenericSignatureCorrectnessHelper.createForVerification(
@@ -1454,7 +1464,8 @@
 
     // Returns the method that shadows the given method, or null if method is not shadowed.
     private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
-      ResolutionResult resolutionResult = appInfo.resolveMethodOn(target, method.getReference());
+      MethodResolutionResult resolutionResult =
+          appInfo.resolveMethodOn(target, method.getReference());
       if (!resolutionResult.isSingleResolution()) {
         // May happen in case of missing classes, or if multiple implementations were found.
         abortMerge = true;
@@ -2018,7 +2029,7 @@
           for (DexType type : method.proto.parameters.values) {
             checkTypeReference(type);
           }
-          ResolutionResult resolutionResult =
+          MethodResolutionResult resolutionResult =
               isInterface.isUnknown()
                   ? appView.appInfo().unsafeResolveMethodDueToDexFormat(method)
                   : appView.appInfo().resolveMethod(method, isInterface.isTrue());
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 8c37bf1..53214f3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -674,12 +674,17 @@
       return true;
     }
     DexProgramClass holder = definition.getHolder().asProgramClass();
+    // TODO(b/192924387): Change such that the keep info for internal synthetics is always bottom.
+    if (!appView.getSyntheticItems().isSubjectToKeepRules(holder)) {
+      return false;
+    }
     KeepInfoCollection keepInfo = appView.getKeepInfo();
-    if (keepInfo.getClassInfo(holder).isPinned()) {
+    InternalOptions options = appView.options();
+    if (keepInfo.getClassInfo(holder).isPinned(options)) {
       return true;
     }
     for (DexEncodedMember<?, ?> member : holder.members()) {
-      if (keepInfo.getMemberInfo(member, holder).isPinned()) {
+      if (keepInfo.getMemberInfo(member, holder).isPinned(options)) {
         return true;
       }
     }
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 d187792..eb97f48 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -669,6 +669,7 @@
     return DexClassAndMethod.create(clazz, methodDefinition);
   }
 
+  @SuppressWarnings("unchecked")
   private <T extends DexClassAndMethod> DexEncodedMethod internalEnsureMethod(
       DexMethod methodReference,
       DexClass clazz,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 4c1ac7c..216fcfc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -51,7 +51,9 @@
     THROW_NSME("ThrowNSME", 16, true),
     TWR_CLOSE_RESOURCE("TwrCloseResource", 17, true),
     SERVICE_LOADER("ServiceLoad", 18, true),
-    OUTLINE("Outline", 19, true);
+    OUTLINE("Outline", 19, true),
+    API_CONVERSION("APIConversion", 26, true),
+    API_CONVERSION_PARAMETERS("APIConversionParameters", 28, true);
 
     static {
       assert verifyNoOverlappingIds();
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 21bb4be..5827d53 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -27,9 +27,9 @@
 import com.android.tools.r8.graph.GraphLens.FieldLookupResult;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
@@ -348,7 +348,7 @@
           return;
         }
         assert lookupResult.getType().isInterface() || lookupResult.getType().isVirtual();
-        ResolutionResult resolutionResult =
+        MethodResolutionResult resolutionResult =
             lookupResult.getType().isInterface()
                 ? appInfo.resolveMethodOnInterface(method)
                 : appInfo.resolveMethodOnClass(method);
diff --git a/src/main/java/com/android/tools/r8/utils/Action.java b/src/main/java/com/android/tools/r8/utils/Action.java
index d69b130..5ab0177 100644
--- a/src/main/java/com/android/tools/r8/utils/Action.java
+++ b/src/main/java/com/android/tools/r8/utils/Action.java
@@ -6,5 +6,12 @@
 
 @FunctionalInterface
 public interface Action {
+
+  Action EMPTY = () -> {};
+
+  static Action empty() {
+    return EMPTY;
+  }
+
   void execute();
 }
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 0a9c18a..c0c5504 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -123,7 +123,7 @@
     }
   }
 
-  public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V15;
+  public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V16_PREVIEW;
   public static final CfVersion EXPERIMENTAL_CF_VERSION = CfVersion.V12;
 
   public static final int SUPPORTED_DEX_VERSION =
@@ -609,8 +609,7 @@
 
   @Override
   public boolean isRepackagingEnabled() {
-    return proguardConfiguration.getPackageObfuscationMode().isSome()
-        && (isMinifying() || testing.repackageWithNoMinification);
+    return proguardConfiguration.getPackageObfuscationMode().isSome() && isMinifying();
   }
 
   @Override
@@ -1221,6 +1220,7 @@
 
     // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
     private boolean enableConstantPropagation = false;
+    private boolean enableExperimentalArgumentPropagation = false;
     private boolean enableTypePropagation = true;
 
     private void disableOptimization() {
@@ -1247,6 +1247,10 @@
       return enableConstantPropagation || enableTypePropagation;
     }
 
+    public boolean isExperimentalArgumentPropagationEnabled() {
+      return enableExperimentalArgumentPropagation;
+    }
+
     public boolean isConstantPropagationEnabled() {
       return enableConstantPropagation;
     }
@@ -1254,6 +1258,16 @@
     public boolean isTypePropagationEnabled() {
       return enableTypePropagation;
     }
+
+    public void setEnableConstantPropagation() {
+      assert !isConstantPropagationEnabled();
+      enableConstantPropagation = true;
+    }
+
+    public void setEnableExperimentalArgumentPropagation() {
+      assert !isExperimentalArgumentPropagationEnabled();
+      enableExperimentalArgumentPropagation = true;
+    }
   }
 
   public class HorizontalClassMergerOptions {
@@ -1403,6 +1417,22 @@
                     }
                     return TraversalContinuation.CONTINUE;
                   }
+
+                  @Override
+                  protected TraversalContinuation visitFields(
+                      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+                      ClassReference holder,
+                      int minApi) {
+                    return null;
+                  }
+
+                  @Override
+                  protected TraversalContinuation visitMethods(
+                      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+                      ClassReference holder,
+                      int minApi) {
+                    return null;
+                  }
                 });
           });
     }
@@ -1544,7 +1574,6 @@
     // TODO(b/177333791): Set to true
     public boolean checkForNotExpandingMainDexTracingResult = false;
     public Set<String> allowedUnusedDontWarnPatterns = new HashSet<>();
-    public boolean repackageWithNoMinification = false;
     public boolean enableTestAssertions =
         System.getProperty("com.android.tools.r8.enableTestAssertions") != null;
     public boolean testEnableTestAssertions = false;
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 68a99e3..1c4aef5 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -62,6 +62,7 @@
     return Collections.unmodifiableList(list);
   }
 
+  @SuppressWarnings("unchecked")
   public static <T, R extends T> R findOrDefault(
       Iterable<T> iterable, Predicate<T> predicate, R defaultValue) {
     for (T element : iterable) {
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index da825d1..ec1b7d0 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -60,6 +60,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -143,17 +144,17 @@
       if (parsedData == null) {
         return baseRemapper.createRemappedPosition(position);
       }
-      Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> currentPosition =
-          parsedData.lookup(line);
-      if (currentPosition == null) {
+      Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> inlinedPosition =
+          parsedData.lookupInlinedPosition(line);
+      if (inlinedPosition == null) {
         return baseRemapper.createRemappedPosition(position);
       }
-      int delta = line - currentPosition.getKey();
-      int originalPosition = currentPosition.getValue().getRange().from + delta;
+      int inlineeLineDelta = line - inlinedPosition.getKey();
+      int originalInlineeLine = inlinedPosition.getValue().getRange().from + inlineeLineDelta;
       try {
-        String binaryName = currentPosition.getValue().getSource().getPath();
+        String binaryName = inlinedPosition.getValue().getSource().getPath();
         String nameAndDescriptor =
-            lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalPosition);
+            lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalInlineeLine);
         if (nameAndDescriptor == null) {
           return baseRemapper.createRemappedPosition(position);
         }
@@ -173,8 +174,20 @@
                 factory.createString(returnTypeDescriptor),
                 argumentDexStringDescriptors);
         if (!inlinee.equals(position.method)) {
+          // We have an inline from a different method than the current position.
+          Entry<Integer, KotlinSourceDebugExtensionParser.Position> calleePosition =
+              parsedData.lookupCalleePosition(line);
+          if (calleePosition != null) {
+            // Take the first line as the callee position
+            position =
+                new Position(
+                    calleePosition.getValue().getRange().from,
+                    position.file,
+                    position.method,
+                    position.callerPosition);
+          }
           return baseRemapper.createRemappedPosition(
-              new Position(originalPosition, null, inlinee, position));
+              new Position(originalInlineeLine, null, inlinee, position));
         }
         // This is the same position, so we should really not mark this as an inline position. Fall
         // through to the default case.
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 91bfbe6..089154e 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -154,13 +154,13 @@
   }
 
   public static <T> ArrayList<T> newArrayList(T element) {
-    ArrayList<T> list = new ArrayList<>();
+    ArrayList<T> list = new ArrayList<>(1);
     list.add(element);
     return list;
   }
 
   public static <T> ArrayList<T> newArrayList(T element, T other) {
-    ArrayList<T> list = new ArrayList<>();
+    ArrayList<T> list = new ArrayList<>(2);
     list.add(element);
     list.add(other);
     return list;
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index 2460a3c..87c0856 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import java.util.IdentityHashMap;
 import java.util.Map;
-import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.IntFunction;
 import java.util.function.Supplier;
@@ -42,6 +42,10 @@
     map.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
   }
 
+  public static <K, V> void removeIf(Map<K, V> map, BiPredicate<K, V> predicate) {
+    map.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
+  }
+
   public static String toString(Map<?, ?> map) {
     return StringUtils.join(
         ",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
@@ -52,7 +56,7 @@
       IntFunction<Map<K2, V2>> factory,
       Function<K1, K2> keyMapping,
       Function<V1, V2> valueMapping,
-      BiFunction<V2, V2, V2> valueMerger) {
+      TriFunction<K2, V2, V2, V2> valueMerger) {
     Map<K2, V2> result = factory.apply(map.size());
     map.forEach(
         (key, value) -> {
@@ -63,7 +67,7 @@
           V2 newValue = valueMapping.apply(value);
           V2 existingValue = result.put(newKey, newValue);
           if (existingValue != null) {
-            result.put(newKey, valueMerger.apply(existingValue, newValue));
+            result.put(newKey, valueMerger.apply(newKey, existingValue, newValue));
           }
         });
     return result;
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 01b58e7..bd97bd7 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -7,6 +7,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Set;
 import java.util.function.Function;
@@ -22,6 +23,12 @@
     return false;
   }
 
+  public static <T> HashSet<T> newHashSet(T element) {
+    HashSet<T> result = new HashSet<>(1);
+    result.add(element);
+    return result;
+  }
+
   public static <T> Set<T> newIdentityHashSet(T element) {
     Set<T> result = Sets.newIdentityHashSet();
     result.add(element);
@@ -85,6 +92,12 @@
     return out;
   }
 
+  public static <T> T removeFirst(Set<T> set) {
+    T element = set.iterator().next();
+    set.remove(element);
+    return element;
+  }
+
   public static <T> Set<T> unionIdentityHashSet(Set<T> one, Set<T> other) {
     Set<T> union = Sets.newIdentityHashSet();
     union.addAll(one);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
index 8ef5506..5122253 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
@@ -94,6 +94,10 @@
     return backing.contains(signature);
   }
 
+  public boolean contains(DexClassAndMethod method) {
+    return contains(method.getMethodSignature());
+  }
+
   @Override
   public boolean containsAll(Collection<?> collection) {
     return backing.containsAll(collection);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
index 24f7e05..dc6a4be 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
@@ -7,12 +7,15 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.utils.ProgramFieldEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
 
 public class ProgramFieldMap<V> extends ProgramMemberMap<ProgramField, V> {
 
+  private static final ProgramFieldMap<?> EMPTY = new ProgramFieldMap<>(ImmutableMap::of);
+
   private ProgramFieldMap(Supplier<Map<Wrapper<ProgramField>, V>> backingFactory) {
     super(backingFactory);
   }
@@ -21,6 +24,11 @@
     return new ProgramFieldMap<>(HashMap::new);
   }
 
+  @SuppressWarnings("unchecked")
+  public static <V> ProgramFieldMap<V> empty() {
+    return (ProgramFieldMap<V>) EMPTY;
+  }
+
   @Override
   Wrapper<ProgramField> wrap(ProgramField method) {
     return ProgramFieldEquivalence.get().wrap(method);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
index a3e1b6b..8fdf054 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ProgramMethodEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -14,6 +15,8 @@
 
 public class ProgramMethodMap<V> extends ProgramMemberMap<ProgramMethod, V> {
 
+  private static final ProgramMethodMap<?> EMPTY = new ProgramMethodMap<>(ImmutableMap::of);
+
   private ProgramMethodMap(Supplier<Map<Wrapper<ProgramMethod>, V>> backingFactory) {
     super(backingFactory);
   }
@@ -26,6 +29,11 @@
     return new ProgramMethodMap<>(ConcurrentHashMap::new);
   }
 
+  @SuppressWarnings("unchecked")
+  public static <V> ProgramMethodMap<V> empty() {
+    return (ProgramMethodMap<V>) EMPTY;
+  }
+
   @Override
   Wrapper<ProgramMethod> wrap(ProgramMethod method) {
     return ProgramMethodEquivalence.get().wrap(method);
diff --git a/src/test/examplesJava16/pattern_matching_for_instanceof/Main.java b/src/test/examplesJava16/pattern_matching_for_instanceof/Main.java
new file mode 100644
index 0000000..35a3632
--- /dev/null
+++ b/src/test/examplesJava16/pattern_matching_for_instanceof/Main.java
@@ -0,0 +1,14 @@
+// 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 pattern_matching_for_instanceof;
+
+final class Main {
+  public static void main(String[] args) {
+    Object obj = "Hello, world!";
+    if (obj instanceof String s) {
+      System.out.println(s);
+    }
+  }
+}
diff --git a/src/test/examplesJava15/records/EmptyRecord.java b/src/test/examplesJava16/records/EmptyRecord.java
similarity index 100%
rename from src/test/examplesJava15/records/EmptyRecord.java
rename to src/test/examplesJava16/records/EmptyRecord.java
diff --git a/src/test/examplesJava15/records/RecordInstanceOf.java b/src/test/examplesJava16/records/RecordInstanceOf.java
similarity index 100%
rename from src/test/examplesJava15/records/RecordInstanceOf.java
rename to src/test/examplesJava16/records/RecordInstanceOf.java
diff --git a/src/test/examplesJava15/records/RecordInvokeCustom.java b/src/test/examplesJava16/records/RecordInvokeCustom.java
similarity index 100%
rename from src/test/examplesJava15/records/RecordInvokeCustom.java
rename to src/test/examplesJava16/records/RecordInvokeCustom.java
diff --git a/src/test/examplesJava15/records/RecordReflection.java b/src/test/examplesJava16/records/RecordReflection.java
similarity index 100%
rename from src/test/examplesJava15/records/RecordReflection.java
rename to src/test/examplesJava16/records/RecordReflection.java
diff --git a/src/test/examplesJava15/records/RecordWithMembers.java b/src/test/examplesJava16/records/RecordWithMembers.java
similarity index 100%
rename from src/test/examplesJava15/records/RecordWithMembers.java
rename to src/test/examplesJava16/records/RecordWithMembers.java
diff --git a/src/test/examplesJava15/records/SimpleRecord.java b/src/test/examplesJava16/records/SimpleRecord.java
similarity index 100%
rename from src/test/examplesJava15/records/SimpleRecord.java
rename to src/test/examplesJava16/records/SimpleRecord.java
diff --git a/src/test/examplesJava15/sealed/Compiler.java b/src/test/examplesJava16/sealed/Compiler.java
similarity index 100%
rename from src/test/examplesJava15/sealed/Compiler.java
rename to src/test/examplesJava16/sealed/Compiler.java
diff --git a/src/test/examplesJava15/sealed/D8Compiler.java b/src/test/examplesJava16/sealed/D8Compiler.java
similarity index 100%
rename from src/test/examplesJava15/sealed/D8Compiler.java
rename to src/test/examplesJava16/sealed/D8Compiler.java
diff --git a/src/test/examplesJava15/sealed/Main.java b/src/test/examplesJava16/sealed/Main.java
similarity index 100%
rename from src/test/examplesJava15/sealed/Main.java
rename to src/test/examplesJava16/sealed/Main.java
diff --git a/src/test/examplesJava15/sealed/R8Compiler.java b/src/test/examplesJava16/sealed/R8Compiler.java
similarity index 100%
rename from src/test/examplesJava15/sealed/R8Compiler.java
rename to src/test/examplesJava16/sealed/R8Compiler.java
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 6d7634c..3908d64 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
 
 public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
   private final String proguardMap;
@@ -54,4 +57,13 @@
   public D8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
     return new D8TestRunResult(app, runtime, result, proguardMap);
   }
+
+  public D8TestRunResult runWithJaCoCo(
+      Path output, TestRuntime runtime, String mainClass, String... args)
+      throws IOException, ExecutionException {
+    setSystemProperty("jacoco-agent.destfile", output.toString());
+    setSystemProperty("jacoco-agent.dumponexit", "true");
+    setSystemProperty("jacoco-agent.output", "file");
+    return run(runtime, mainClass, args);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 9951a04..f92a490 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -158,6 +158,23 @@
     return self();
   }
 
+  public JvmTestBuilder enableJaCoCoAgentForOfflineInstrumentedCode(Path jacocoAgent, Path output) {
+    addProgramFiles(jacocoAgent);
+    addVmArguments(
+        "-Djacoco-agent.destfile=" + output.toString(),
+        "-Djacoco-agent.dumponexit=true",
+        "-Djacoco-agent.output=file");
+    return self();
+  }
+
+  public JvmTestBuilder enableJaCoCoAgent(Path jacocoAgent, Path output) {
+    addProgramFiles(jacocoAgent);
+    addVmArguments(
+        String.format(
+            "-javaagent:%s=destfile=%s,dumponexit=true,output=file", jacocoAgent, output));
+    return self();
+  }
+
   public JvmTestBuilder addVmArguments(Collection<String> arguments) {
     vmArguments.addAll(arguments);
     return self();
diff --git a/src/test/java/com/android/tools/r8/R8CfVersionTest.java b/src/test/java/com/android/tools/r8/R8CfVersionTest.java
index 8e9b11c..fcc29c1 100644
--- a/src/test/java/com/android/tools/r8/R8CfVersionTest.java
+++ b/src/test/java/com/android/tools/r8/R8CfVersionTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -43,6 +44,8 @@
 
   @Test
   public void testCfVersionR8Lib() throws IOException {
+    // Only run when testing R8 lib as only then do we know it is built and up-to-date.
+    assumeTrue(ToolHelper.isTestingR8Lib());
     CodeInspector inspector = new CodeInspector(ToolHelper.R8LIB_JAR);
     inspector.forAllClasses(
         clazz -> {
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index a51fcf0..6fd2f96 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -29,7 +29,7 @@
     JDK9("jdk9", 53),
     JDK10("jdk10", 54),
     JDK11("jdk11", 55),
-    JDK15("jdk15", 59),
+    JDK16("jdk16", 60),
     ;
 
     private final String name;
@@ -70,13 +70,13 @@
   private static final Path JDK9_PATH =
       Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "openjdk-9.0.4");
   private static final Path JDK11_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-11");
-  private static final Path JDK15_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-15");
+  private static final Path JDK16_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-16");
   private static final Map<CfVm, Path> jdkPaths =
       ImmutableMap.of(
           CfVm.JDK8, JDK8_PATH,
           CfVm.JDK9, JDK9_PATH,
           CfVm.JDK11, JDK11_PATH,
-          CfVm.JDK15, JDK15_PATH);
+          CfVm.JDK16, JDK16_PATH);
 
   public static CfRuntime getCheckedInJdk(CfVm vm) {
     if (vm == CfVm.JDK8) {
@@ -121,9 +121,9 @@
     return new CfRuntime(CfVm.JDK11, getCheckedInJdkHome(CfVm.JDK11));
   }
 
-  // TODO(b/169692487): Add this to 'getCheckedInCfRuntimes' when we start having support for JDK15.
-  public static CfRuntime getCheckedInJdk15() {
-    return new CfRuntime(CfVm.JDK15, getCheckedInJdkHome(CfVm.JDK15));
+  // TODO(b/169692487): Add this to 'getCheckedInCfRuntimes' when we start having support for JDK16.
+  public static CfRuntime getCheckedInJdk16() {
+    return new CfRuntime(CfVm.JDK16, getCheckedInJdkHome(CfVm.JDK16));
   }
 
   public static List<CfRuntime> getCheckedInCfRuntimes() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f0d2830..a1bdeb8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -151,9 +151,9 @@
   private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard";
   private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
   private static final String PROGUARD = PROGUARD5_2_1;
-  public static final String JACOCO_AGENT =
-      "third_party/jacoco/0.8.2/org.jacoco.agent-0.8.2-runtime.jar";
-  public static final String JACOCO_CLI = "third_party/jacoco/0.8.6/lib/jacococli.jar";
+  public static final Path JACOCO_ROOT = Paths.get("third_party", "jacoco", "0.8.6");
+  public static final Path JACOCO_AGENT = JACOCO_ROOT.resolve(Paths.get("lib", "jacocoagent.jar"));
+  public static final Path JACOCO_CLI = JACOCO_ROOT.resolve(Paths.get("lib", "jacococli.jar"));
   public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS = "third_party/proguardsettings/";
 
   private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
@@ -1593,6 +1593,36 @@
     return processResult;
   }
 
+  public static ProcessResult runJaCoCoInstrument(Path sourceClassFiles, Path outputDirectory)
+      throws IOException {
+    List<String> cmdline = new ArrayList<>();
+    cmdline.add(TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString());
+    cmdline.add("-jar");
+    cmdline.add(ToolHelper.JACOCO_CLI.toString());
+    cmdline.add("instrument");
+    cmdline.add(sourceClassFiles.toString());
+    cmdline.add("--dest");
+    cmdline.add(outputDirectory.toString());
+    ProcessBuilder builder = new ProcessBuilder(cmdline);
+    return ToolHelper.runProcess(builder);
+  }
+
+  public static ProcessResult runJaCoCoReport(Path classfiles, Path jacocoExec, Path reportFile)
+      throws IOException {
+    List<String> cmdline = new ArrayList<>();
+    cmdline.add(TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString());
+    cmdline.add("-jar");
+    cmdline.add(ToolHelper.JACOCO_CLI.toString());
+    cmdline.add("report");
+    cmdline.add(jacocoExec.toString());
+    cmdline.add("--classfiles");
+    cmdline.add(classfiles.toString());
+    cmdline.add("--csv");
+    cmdline.add(reportFile.toString());
+    ProcessBuilder builder = new ProcessBuilder(cmdline);
+    return ToolHelper.runProcess(builder);
+  }
+
   private static Path findNonConflictingDestinationFilePath(Path testOutputPath) {
     int index = 0;
     Path destFilePath;
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonPublicKeptClassPublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonPublicKeptClassPublicizerTest.java
new file mode 100644
index 0000000..bd4bf25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonPublicKeptClassPublicizerTest.java
@@ -0,0 +1,57 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.lang.reflect.Modifier;
+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 NonPublicKeptClassPublicizerTest 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)
+        .allowAccessModification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+              assertThat(mainClassSubject, not(isPublic()));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("false");
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      System.out.println(Modifier.isPublic(Main.class.getModifiers()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
index dfaecd1..8c139fe 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
@@ -13,6 +13,7 @@
 import static org.objectweb.asm.Opcodes.DUP;
 import static org.objectweb.asm.Opcodes.F_SAME1;
 import static org.objectweb.asm.Opcodes.IFEQ;
+import static org.objectweb.asm.Opcodes.ILOAD;
 import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
@@ -27,7 +28,9 @@
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.IntBox;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -89,10 +92,11 @@
             .collect(Collectors.toList());
 
     for (ParsedApiClass apiClass : apiClasses) {
+      String apiClassDescriptor = getApiClassReference(apiClass).getDescriptor();
       consumer.accept(
-          getApiClassDescriptor(apiClass),
+          apiClassDescriptor,
           transformer(AndroidApiDatabaseClassTemplate.class)
-              .setClassDescriptor(getApiClassDescriptor(apiClass))
+              .setClassDescriptor(apiClassDescriptor)
               .addMethodTransformer(getInitTransformer(apiClass))
               .addMethodTransformer(getApiLevelTransformer(apiClass))
               .addMethodTransformer(getGetMemberCountTransformer(apiClass))
@@ -103,6 +107,8 @@
               .removeMethods(MethodPredicate.onName("placeHolderForGetMemberCount"))
               .removeMethods(MethodPredicate.onName("placeHolderForVisitFields"))
               .removeMethods(MethodPredicate.onName("placeHolderForVisitMethods"))
+              .setSourceFile(
+                  DescriptorUtils.getSimpleClassNameFromDescriptor(apiClassDescriptor) + ".java")
               .computeMaxs()
               .transform(ClassWriter.COMPUTE_MAXS));
     }
@@ -137,13 +143,17 @@
             .replace("Template", "ForPackage_" + pkg.replace(".", "_")));
   }
 
-  private static String getApiClassDescriptor(ParsedApiClass apiClass) {
-    return DescriptorUtils.javaTypeToDescriptor(
-        AndroidApiDatabaseClassTemplate.class
-            .getTypeName()
-            .replace(
-                "Template",
-                "ForClass_" + apiClass.getClassReference().getTypeName().replace(".", "_")));
+  private static ClassReference getApiClassReference(ParsedApiClass apiClass) {
+    return fromTemplate(apiClass.getClassReference());
+  }
+
+  private static ClassReference fromTemplate(ClassReference classReference) {
+    String descriptor =
+        DescriptorUtils.javaTypeToDescriptor(
+            AndroidApiDatabaseClassTemplate.class
+                .getTypeName()
+                .replace("Template", "ForClass_" + classReference.getTypeName().replace(".", "_")));
+    return Reference.classFromDescriptor(descriptor);
   }
 
   // The transformer below changes AndroidApiDatabaseClassTemplate.<init> from:
@@ -190,17 +200,24 @@
   //     placeHolder();
   //     return TraversalContinuation.CONTINUE;
   // into
-  //    TraversalContinuation s1 = visitField("field1", "descriptor1", apiLevel1, visitor)
+  //    TraversalContinuation s1 = visitField(visitor, holder, minApiClass, apiLevel1, "field1",
+  //        "descriptor1")
   //    if (s1.shouldBreak()) {
   //       return s1;
   //    }
-  //    TraversalContinuation s2 = visitField("field2", "descriptor2", apiLevel2, visitor)
+  //    TraversalContinuation s2 = visitField(visitor, holder, minApiClass, apiLevel2, "field2",
+  //   //        "descriptor2")
   //    if (s2.shouldBreak()) {
   //       return s2;
   //    }
   //    ...
+  //    AndroidApiClassForClass_class_name1() super1 = new AndroidApiClassForClass_class_name1();
+  //    TraversalContinuation sN = super1.visitFields(
+  //        visitor, holder, max(minApiClass, minApiForLink));
+  //    ...
   //    return TraversalContinuation.CONTINUE;
   private static MethodTransformer getVisitFieldsTransformer(ParsedApiClass apiClass) {
+    IntBox lineNumberBox = new IntBox(0);
     return replaceCode(
         "placeHolderForVisitFields",
         transformer -> {
@@ -208,19 +225,26 @@
               (apiLevel, references) -> {
                 references.forEach(
                     reference -> {
+                      Label labelStart = new Label();
+                      transformer.visitLabel(labelStart);
+                      transformer.visitLineNumber(lineNumberBox.getAndIncrement(), labelStart);
                       transformer.visitVarInsn(ALOAD, 0);
+                      transformer.visitVarInsn(ALOAD, 1);
+                      transformer.visitVarInsn(ALOAD, 2);
+                      transformer.visitVarInsn(ILOAD, 3);
+                      transformer.visitLdcInsn(apiLevel.getLevel());
                       transformer.visitLdcInsn(reference.getFieldName());
                       transformer.visitLdcInsn(reference.getFieldType().getDescriptor());
-                      transformer.visitLdcInsn(apiLevel.getLevel());
-                      transformer.visitVarInsn(ALOAD, 1);
                       transformer.visitMethodInsn(
                           INVOKEVIRTUAL,
                           ANDROID_API_CLASS.getBinaryName(),
                           "visitField",
-                          "(Ljava/lang/String;"
-                              + "Ljava/lang/String;"
+                          "(Ljava/util/function/BiFunction;"
+                              + descriptor(ClassReference.class)
                               + "I"
-                              + "Ljava/util/function/BiFunction;)"
+                              + "I"
+                              + "Ljava/lang/String;"
+                              + "Ljava/lang/String;)"
                               + TRAVERSAL_CONTINUATION.getDescriptor(),
                           false);
                       // Note that instead of storing the result here, we dup it on the stack.
@@ -246,6 +270,19 @@
                       transformer.visitInsn(POP);
                     });
               });
+          if (!apiClass.isInterface()) {
+            apiClass.visitSuperType(
+                (superType, apiLevel) -> {
+                  addMembersForParent(
+                      transformer,
+                      superType,
+                      "visitFields",
+                      apiLevel,
+                      lineNumberBox.getAndIncrement());
+                });
+          }
+          // No need to visit fields on interfaces since they have to be static and should not be
+          // called on a super instance.
         });
   }
 
@@ -253,19 +290,24 @@
   //     placeHolderForVisitMethods();
   //     return TraversalContinuation.CONTINUE;
   // into
-  //    TraversalContinuation s1 = visitMethod(
-  //      "method1", new String[] { "param11", ... , "param1X" }, null/return1, apiLevel1, visitor)
+  //    TraversalContinuation s1 = visitMethod(visitor, holder, minApiClass, apiLevel1,
+  //      "method1", new String[] { "param11", ... , "param1X" }, null/return1)
   //    if (s1.shouldBreak()) {
   //       return s1;
   //    }
-  //    TraversalContinuation s1 = visitMethod(
-  //      "method2", new String[] { "param21", ... , "param2X" }, null/return2, apiLevel2, visitor)
+  //    TraversalContinuation s1 = visitMethod(visitor, holder, minApiClass, apiLevel2,
+  //      "method2", new String[] { "param21", ... , "param2X" }, null/return2)
   //    if (s2.shouldBreak()) {
   //       return s2;
   //    }
   //    ...
+  //    AndroidApiClassForClass_class_name1() super1 = new AndroidApiClassForClass_class_name1();
+  //    TraversalContinuation sN = super1.visitMethods(
+  //      visitor, holder, max(minApiClass, minApiForLink));
+  //    ...
   //    return TraversalContinuation.CONTINUE;
   private static MethodTransformer getVisitMethodsTransformer(ParsedApiClass apiClass) {
+    IntBox lineNumberBox = new IntBox(0);
     return replaceCode(
         "placeHolderForVisitMethods",
         transformer -> {
@@ -273,7 +315,14 @@
               (apiLevel, references) -> {
                 references.forEach(
                     reference -> {
+                      Label labelStart = new Label();
+                      transformer.visitLabel(labelStart);
+                      transformer.visitLineNumber(lineNumberBox.getAndIncrement(), labelStart);
                       transformer.visitVarInsn(ALOAD, 0);
+                      transformer.visitVarInsn(ALOAD, 1);
+                      transformer.visitVarInsn(ALOAD, 2);
+                      transformer.visitVarInsn(ILOAD, 3);
+                      transformer.visitLdcInsn(apiLevel.getLevel());
                       transformer.visitLdcInsn(reference.getMethodName());
                       List<TypeReference> formalTypes = reference.getFormalTypes();
                       transformer.visitLdcInsn(formalTypes.size());
@@ -289,16 +338,17 @@
                       } else {
                         transformer.visitInsn(ACONST_NULL);
                       }
-                      transformer.visitLdcInsn(apiLevel.getLevel());
-                      transformer.visitVarInsn(ALOAD, 1);
                       transformer.visitMethodInsn(
                           INVOKEVIRTUAL,
                           ANDROID_API_CLASS.getBinaryName(),
                           "visitMethod",
-                          "(Ljava/lang/String;"
-                              + "[Ljava/lang/String;Ljava/lang/String;"
+                          "(Ljava/util/function/BiFunction;"
+                              + descriptor(ClassReference.class)
                               + "I"
-                              + "Ljava/util/function/BiFunction;)"
+                              + "I"
+                              + "Ljava/lang/String;"
+                              + "[Ljava/lang/String;"
+                              + "Ljava/lang/String;)"
                               + TRAVERSAL_CONTINUATION.getDescriptor(),
                           false);
                       // Note that instead of storing the result here, we dup it on the stack.
@@ -324,9 +374,72 @@
                       transformer.visitInsn(POP);
                     });
               });
+          if (!apiClass.isInterface()) {
+            // Visit super types before interfaces emulating a poor man's resolutions.
+            apiClass.visitSuperType(
+                (superType, apiLevel) -> {
+                  addMembersForParent(
+                      transformer,
+                      superType,
+                      "visitMethods",
+                      apiLevel,
+                      lineNumberBox.getAndIncrement());
+                });
+          }
+          apiClass.visitInterface(
+              (classReference, apiLevel) -> {
+                addMembersForParent(
+                    transformer,
+                    classReference,
+                    "visitMethods",
+                    apiLevel,
+                    lineNumberBox.getAndIncrement());
+              });
         });
   }
 
+  private static void addMembersForParent(
+      MethodTransformer transformer,
+      ClassReference classReference,
+      String methodName,
+      AndroidApiLevel minApiLevel,
+      int lineNumber) {
+    String binaryName = fromTemplate(classReference).getBinaryName();
+    Label labelStart = new Label();
+    transformer.visitLabel(labelStart);
+    transformer.visitLineNumber(lineNumber, labelStart);
+    transformer.visitTypeInsn(NEW, binaryName);
+    transformer.visitInsn(DUP);
+    transformer.visitMethodInsn(INVOKESPECIAL, binaryName, "<init>", "()V", false);
+    transformer.visitVarInsn(ALOAD, 1);
+    transformer.visitVarInsn(ALOAD, 2);
+    // Compute the max api level compared to what is passed in.
+    transformer.visitLdcInsn(minApiLevel.getLevel());
+    transformer.visitVarInsn(ILOAD, 3);
+    transformer.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "max", "(II)I", false);
+    transformer.visitMethodInsn(
+        INVOKEVIRTUAL,
+        binaryName,
+        methodName,
+        "(Ljava/util/function/BiFunction;"
+            + descriptor(ClassReference.class)
+            + "I)"
+            + TRAVERSAL_CONTINUATION.getDescriptor(),
+        false);
+    // Note that instead of storing the result here, we dup it on the stack.
+    transformer.visitInsn(DUP);
+    transformer.visitMethodInsn(
+        INVOKEVIRTUAL, TRAVERSAL_CONTINUATION.getBinaryName(), "shouldBreak", "()Z", false);
+    Label label = new Label();
+    transformer.visitJumpInsn(IFEQ, label);
+    transformer.visitInsn(ARETURN);
+    transformer.visitLabel(label);
+    transformer.visitFrame(
+        F_SAME1, 0, new Object[] {}, 1, new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
+    // The pop here is needed to remove the dupped value in the case we do not return.
+    transformer.visitInsn(POP);
+  }
+
   // The transformer below changes AndroidApiDatabasePackageTemplate.buildClass from:
   //    placeHolder();
   //    return null;
@@ -359,8 +472,7 @@
                     false);
                 Label label = new Label();
                 transformer.visitJumpInsn(IFEQ, label);
-                String binaryName =
-                    DescriptorUtils.getBinaryNameFromDescriptor(getApiClassDescriptor(apiClass));
+                String binaryName = getApiClassReference(apiClass).getBinaryName();
                 transformer.visitTypeInsn(NEW, binaryName);
                 transformer.visitInsn(DUP);
                 transformer.visitMethodInsn(INVOKESPECIAL, binaryName, "<init>", "()V", false);
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
index 2ad1e72..8b3571a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanBox;
@@ -36,7 +37,9 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiFunction;
 import org.junit.Test;
@@ -121,6 +124,8 @@
    * were introduced. Running main will generate a new jar and run tests on it to ensure it is
    * compatible with R8 sources and works as expected.
    *
+   * <p>The generated jar depends on r8NoManifestWithoutDeps.
+   *
    * <p>If the generated jar passes tests it will be moved to third_party/android_jar/api-database/
    * and override the current file in there.
    */
@@ -141,8 +146,12 @@
             AndroidApiDatabaseBuilderGeneratorTest::testNoPlaceHolder);
     tests.forEach(
         test -> {
-          if (!test.apply(generated, apiClasses)) {
-            throw new RuntimeException("Generated jar did not pass tests");
+          try {
+            if (!test.apply(generated, apiClasses)) {
+              throw new RuntimeException("Generated jar did not pass tests");
+            }
+          } catch (Exception e) {
+            throw new RuntimeException("Generated jar did not pass tests", e);
           }
         });
   }
@@ -232,44 +241,116 @@
   }
 
   private static String getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
+    Map<ClassReference, ParsedApiClass> parsedApiClassMap = new HashMap<>(parsedApiClasses.size());
+    parsedApiClasses.forEach(
+        parsedClass -> parsedApiClassMap.put(parsedClass.getClassReference(), parsedClass));
     List<String> expected = new ArrayList<>();
     parsedApiClasses.forEach(
         apiClass -> {
-          expected.add(apiClass.getClassReference().getDescriptor());
+          expected.add("CLASS: " + apiClass.getClassReference().getDescriptor());
           expected.add(apiClass.getApiLevel().getName());
           expected.add(apiClass.getTotalMemberCount() + "");
-          BooleanBox added = new BooleanBox(false);
-          apiClass.visitFieldReferences(
-              (apiLevel, fieldReferences) -> {
-                fieldReferences.forEach(
-                    fieldReference -> {
-                      if (added.isTrue() && abort) {
-                        return;
-                      }
-                      added.set();
-                      expected.add(fieldReference.getFieldType().getDescriptor());
-                      expected.add(fieldReference.getFieldName());
-                      expected.add(apiLevel.getName());
-                    });
-              });
-          added.set(false);
-          apiClass.visitMethodReferences(
-              (apiLevel, methodReferences) -> {
-                methodReferences.forEach(
-                    methodReference -> {
-                      if (added.isTrue() && abort) {
-                        return;
-                      }
-                      added.set();
-                      expected.add(methodReference.getMethodDescriptor());
-                      expected.add(methodReference.getMethodName());
-                      expected.add(apiLevel.getName());
-                    });
-              });
+          visitApiClass(expected, parsedApiClassMap, apiClass, apiClass.getApiLevel(), true, abort);
+          visitApiClass(
+              expected, parsedApiClassMap, apiClass, apiClass.getApiLevel(), false, abort);
         });
     return StringUtils.lines(expected);
   }
 
+  private static boolean visitApiClass(
+      List<String> expected,
+      Map<ClassReference, ParsedApiClass> parsedApiClassMap,
+      ParsedApiClass apiClass,
+      AndroidApiLevel apiLevel,
+      boolean visitFields,
+      boolean abort) {
+    BooleanBox added = new BooleanBox(false);
+    if (visitFields) {
+      added.set(visitFields(expected, apiClass, apiLevel, abort));
+    } else {
+      added.set(visitMethods(expected, apiClass, apiLevel, abort));
+    }
+    if (added.isTrue() && abort) {
+      return true;
+    }
+    // Go through super type methods if not interface.
+    if (!apiClass.isInterface()) {
+      apiClass.visitSuperType(
+          (classReference, linkApiLevel) -> {
+            if (added.isTrue() && abort) {
+              return;
+            }
+            ParsedApiClass superApiClass = parsedApiClassMap.get(classReference);
+            assert superApiClass != null;
+            added.set(
+                visitApiClass(
+                    expected,
+                    parsedApiClassMap,
+                    superApiClass,
+                    linkApiLevel.max(apiLevel),
+                    visitFields,
+                    abort));
+          });
+    }
+    if (!visitFields) {
+      apiClass.visitInterface(
+          (classReference, linkApiLevel) -> {
+            if (added.isTrue() && abort) {
+              return;
+            }
+            ParsedApiClass ifaceApiClass = parsedApiClassMap.get(classReference);
+            assert ifaceApiClass != null;
+            added.set(
+                visitApiClass(
+                    expected,
+                    parsedApiClassMap,
+                    ifaceApiClass,
+                    linkApiLevel.max(apiLevel),
+                    visitFields,
+                    abort));
+          });
+    }
+    return added.get();
+  }
+
+  private static boolean visitFields(
+      List<String> expected, ParsedApiClass apiClass, AndroidApiLevel minApiLevel, boolean abort) {
+    BooleanBox added = new BooleanBox(false);
+    apiClass.visitFieldReferences(
+        (apiLevel, fieldReferences) -> {
+          fieldReferences.forEach(
+              fieldReference -> {
+                if (added.isTrue() && abort) {
+                  return;
+                }
+                added.set();
+                expected.add(fieldReference.getFieldType().getDescriptor());
+                expected.add(fieldReference.getFieldName());
+                expected.add(apiLevel.max(minApiLevel).getName());
+              });
+        });
+    return added.get();
+  }
+
+  private static boolean visitMethods(
+      List<String> expected, ParsedApiClass apiClass, AndroidApiLevel minApiLevel, boolean abort) {
+    BooleanBox added = new BooleanBox(false);
+    apiClass.visitMethodReferences(
+        (apiLevel, methodReferences) -> {
+          methodReferences.forEach(
+              methodReference -> {
+                if (added.isTrue() && abort) {
+                  return;
+                }
+                added.set();
+                expected.add(methodReference.getMethodDescriptor());
+                expected.add(methodReference.getMethodName());
+                expected.add(apiLevel.max(minApiLevel).getName());
+              });
+        });
+    return added.get();
+  }
+
   public static class TestGeneratedMainVisitClasses {
 
     public static void main(String[] args) {
@@ -286,7 +367,7 @@
                 AndroidApiDatabaseBuilderTemplate.buildClass(
                     Reference.classFromDescriptor(descriptor));
             if (apiClass != null) {
-              System.out.println(descriptor);
+              System.out.println("CLASS: " + descriptor);
               System.out.println(apiClass.getApiLevel().getName());
               System.out.println(apiClass.getMemberCount());
               apiClass.visitFields(
@@ -317,7 +398,7 @@
                 AndroidApiDatabaseBuilderTemplate.buildClass(
                     Reference.classFromDescriptor(descriptor));
             if (apiClass != null) {
-              System.out.println(descriptor);
+              System.out.println("CLASS: " + descriptor);
               System.out.println(apiClass.getApiLevel().getName());
               System.out.println(apiClass.getMemberCount());
               apiClass.visitFields(
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
index faefd62..1a8b7e3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.apimodel;
 
 import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
@@ -34,7 +35,9 @@
 
   @Override
   public TraversalContinuation visitFields(
-      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+      BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+      ClassReference holder,
+      int minApi) {
     // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
     placeHolderForVisitFields();
     return TraversalContinuation.CONTINUE;
@@ -42,7 +45,9 @@
 
   @Override
   public TraversalContinuation visitMethods(
-      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+      BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+      ClassReference holder,
+      int minApi) {
     // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
     placeHolderForVisitMethods();
     return TraversalContinuation.CONTINUE;
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 4002bf9..24b9613 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -19,6 +19,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -40,8 +41,9 @@
     this.maxApiLevel = maxApiLevel;
   }
 
-  private ParsedApiClass register(ClassReference reference, AndroidApiLevel apiLevel) {
-    ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel);
+  private ParsedApiClass register(
+      ClassReference reference, AndroidApiLevel apiLevel, boolean isInterface) {
+    ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel, isInterface);
     classes.add(parsedApiClass);
     return parsedApiClass;
   }
@@ -62,11 +64,19 @@
         continue;
       }
       ClassReference originalReference = clazz.getOriginalReference();
-      ParsedApiClass parsedApiClass = register(originalReference, apiLevel);
+      ParsedApiClass parsedApiClass = register(originalReference, apiLevel, clazz.isInterface());
       NodeList members = node.getChildNodes();
       for (int j = 0; j < members.getLength(); j++) {
         Node memberNode = members.item(j);
-        if (isMethod(memberNode)) {
+        if (isExtends(memberNode)) {
+          parsedApiClass.registerSuperType(
+              Reference.classFromBinaryName(getName(memberNode)),
+              hasSince(memberNode) ? getSince(memberNode) : apiLevel);
+        } else if (isImplements(memberNode)) {
+          parsedApiClass.registerInterface(
+              Reference.classFromBinaryName(getName(memberNode)),
+              hasSince(memberNode) ? getSince(memberNode) : apiLevel);
+        } else if (isMethod(memberNode)) {
           // TODO(b/190326408): Check for existence.
           parsedApiClass.register(
               getMethodReference(originalReference, memberNode),
@@ -109,16 +119,29 @@
     return node.getNodeName().equals("field");
   }
 
-  private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
-    if (node == null) {
-      return defaultValue;
-    }
+  private boolean isExtends(Node node) {
+    return node.getNodeName().equals("extends");
+  }
+
+  private boolean isImplements(Node node) {
+    return node.getNodeName().equals("implements");
+  }
+
+  private boolean hasSince(Node node) {
+    return node.getAttributes().getNamedItem("since") != null;
+  }
+
+  private AndroidApiLevel getSince(Node node) {
+    assert hasSince(node);
     Node since = node.getAttributes().getNamedItem("since");
-    if (since == null) {
+    return AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue()));
+  }
+
+  private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
+    if (node == null || !hasSince(node)) {
       return defaultValue;
     }
-    return defaultValue.max(
-        AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue())));
+    return defaultValue.max(getSince(node));
   }
 
   public static List<ParsedApiClass> getParsedApiClasses(
@@ -132,12 +155,17 @@
 
     private final ClassReference classReference;
     private final AndroidApiLevel apiLevel;
+    private final boolean isInterface;
+    private final Map<ClassReference, AndroidApiLevel> superTypes = new LinkedHashMap<>();
+    private final Map<ClassReference, AndroidApiLevel> interfaces = new LinkedHashMap<>();
     private final TreeMap<AndroidApiLevel, List<FieldReference>> fieldReferences = new TreeMap<>();
     private final Map<AndroidApiLevel, List<MethodReference>> methodReferences = new TreeMap<>();
 
-    private ParsedApiClass(ClassReference classReference, AndroidApiLevel apiLevel) {
+    private ParsedApiClass(
+        ClassReference classReference, AndroidApiLevel apiLevel, boolean isInterface) {
       this.classReference = classReference;
       this.apiLevel = apiLevel;
+      this.isInterface = isInterface;
     }
 
     public ClassReference getClassReference() {
@@ -171,6 +199,16 @@
       methodReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
     }
 
+    private void registerSuperType(ClassReference superType, AndroidApiLevel apiLevel) {
+      AndroidApiLevel existing = superTypes.put(superType, apiLevel);
+      assert existing == null;
+    }
+
+    private void registerInterface(ClassReference iface, AndroidApiLevel apiLevel) {
+      AndroidApiLevel existing = interfaces.put(iface, apiLevel);
+      assert existing == null;
+    }
+
     public void visitFieldReferences(BiConsumer<AndroidApiLevel, List<FieldReference>> consumer) {
       fieldReferences.forEach(
           (apiLevel, references) -> {
@@ -186,5 +224,17 @@
             consumer.accept(apiLevel, references);
           });
     }
+
+    public void visitSuperType(BiConsumer<ClassReference, AndroidApiLevel> consumer) {
+      superTypes.forEach(consumer);
+    }
+
+    public void visitInterface(BiConsumer<ClassReference, AndroidApiLevel> consumer) {
+      interfaces.forEach(consumer);
+    }
+
+    public boolean isInterface() {
+      return isInterface;
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java
index 32b111e..5b47c79 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java
@@ -5,10 +5,8 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -35,7 +33,7 @@
     this.parameters = parameters;
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testR8() throws Exception {
     Method main = Main.class.getDeclaredMethod("main", String[].class);
     testForR8(parameters.getBackend())
@@ -55,16 +53,14 @@
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
                   if (Reference.methodFromMethod(main).equals(method)) {
-                    // TODO(b/193414761): Should not be UNKNOWN.
-                    assertEquals(AndroidApiLevel.UNKNOWN, apiLevel);
+                    assertEquals(
+                        parameters.isCfRuntime()
+                            ? AndroidApiLevel.E
+                            : parameters.getApiLevel().max(AndroidApiLevel.E),
+                        apiLevel);
                   }
                 }))
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              // TODO(b/193414761): We should analyze all members.
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Every member should have been analyzed"));
-            });
+        .compile();
   }
 
   /* Only here to get the test to compile */
@@ -77,7 +73,7 @@
 
     public static void main(String[] args) {
       // START_CONTINUATION_MASK is inherited from android/app/Service which was introduced at
-      // AndroidApiLevel.B.
+      // AndroidApiLevel.E.
       System.out.println(
           new /* android.accessibilityservice */ AccessibilityService().START_CONTINUATION_MASK);
     }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java
index 22c0702..892f9d8 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java
@@ -5,15 +5,13 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static com.android.tools.r8.utils.AndroidApiLevel.UNKNOWN;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import org.junit.Test;
@@ -35,7 +33,7 @@
     this.parameters = parameters;
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testR8() throws Exception {
     Method main = Main.class.getDeclaredMethod("main", String[].class);
     testForR8(parameters.getBackend())
@@ -48,16 +46,14 @@
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
                   if (Reference.methodFromMethod(main).equals(method)) {
-                    // TODO(b/193414761): Should not be UNKNOWN.
-                    assertEquals(UNKNOWN, apiLevel);
+                    assertEquals(
+                        parameters.isCfRuntime()
+                            ? AndroidApiLevel.B
+                            : parameters.getApiLevel().max(AndroidApiLevel.B),
+                        apiLevel);
                   }
                 }))
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              // TODO(b/193414761): We should analyze all members.
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Every member should have been analyzed"));
-            });
+        .compile();
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
index 3f8bed1..04337ec 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
@@ -6,11 +6,9 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
 import static com.android.tools.r8.utils.AndroidApiLevel.LATEST;
-import static com.android.tools.r8.utils.AndroidApiLevel.UNKNOWN;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.AndroidApiLevel.R;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -36,7 +34,7 @@
     this.parameters = parameters;
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testR8() throws Exception {
     // Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction; is introduced at api
     // level 21 and on api level 30 it implements android.os.Parcelable.
@@ -58,16 +56,10 @@
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
                   if (Reference.methodFromMethod(main).equals(method)) {
-                    // TODO(b/193414761): Should be 30.
-                    assertEquals(UNKNOWN, apiLevel);
+                    assertEquals(R, apiLevel);
                   }
                 }))
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              // TODO(b/193414761): We should analyze all members.
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Every member should have been analyzed"));
-            });
+        .compile();
   }
 
   public static class AccessibilityNodeInfo$AccessibilityAction {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java
index 2c5868e..c79c69b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java
@@ -5,10 +5,8 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -35,7 +33,7 @@
     this.parameters = parameters;
   }
 
-  @Test(expected = CompilationFailedException.class)
+  @Test
   public void testR8() throws Exception {
     Method main = Main.class.getDeclaredMethod("main", String[].class);
     testForR8(parameters.getBackend())
@@ -55,16 +53,17 @@
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
                   if (Reference.methodFromMethod(main).equals(method)) {
-                    // TODO(b/193414761): Should not be UNKNOWN.
-                    assertEquals(AndroidApiLevel.UNKNOWN, apiLevel);
+                    // android.app.Service.stopSelf() was introduced at AndroidApiLevel.B but
+                    // android/accessibilityservice/AccessibilityService was introduced at D
+                    // so the minimum api level is D.
+                    assertEquals(
+                        parameters.isCfRuntime()
+                            ? AndroidApiLevel.D
+                            : parameters.getApiLevel().max(AndroidApiLevel.D),
+                        apiLevel);
                   }
                 }))
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              // TODO(b/193414761): We should analyze all members.
-              diagnostics.assertErrorMessageThatMatches(
-                  containsString("Every member should have been analyzed"));
-            });
+        .compile();
   }
 
   /* Only here to get the test to compile */
@@ -76,7 +75,8 @@
   public static class Main {
 
     public static void main(String[] args) {
-      // stopSelf() is inherited from android/app/Service which was introduced at AndroidApiLevel.B.
+      // AccessibilityService.stopSelf() is inherited from android/app/Service which was introduced
+      // at AndroidApiLevel.B.
       new /* android.accessibilityservice */ AccessibilityService().stopSelf();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/AbstractAfterTreeShakingBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/AbstractAfterTreeShakingBridgeHoistingTest.java
new file mode 100644
index 0000000..772ef4f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/AbstractAfterTreeShakingBridgeHoistingTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.bridgeremoval.hoisting;
+
+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.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/** Regression test for b/195037294. */
+@RunWith(Parameterized.class)
+public class AbstractAfterTreeShakingBridgeHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AbstractAfterTreeShakingBridgeHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(
+            transformer(B1.class)
+                .setBridge(B1.class.getDeclaredMethod("virtualBridge", Object.class))
+                .transform(),
+            transformer(B2.class)
+                .setBridge(B2.class.getDeclaredMethod("virtualBridge", Object.class))
+                .transform())
+        .addKeepMainRule(TestClass.class)
+        // Keep test() method to disable call site optimization for that method.
+        .addKeepRules(
+            "-keepclassmembers class " + TestClass.class.getTypeName() + " {",
+            "  void test(...);",
+            "}")
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput("Hello");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      B1 b1 = null;
+      B2 b2 = null;
+      // Dead instantiations of B1 and B2. This way, R8 considers B1 and B2 to be instantiated until
+      // the second round of tree shaking. After the second round of tree shaking, the
+      // virtualBridge() methods on B1 and B2 are made abstract, since they are not instantiated.
+      // This should disable bridge hoisting.
+      if (alwaysFalse()) {
+        b1 = B1.create();
+        b2 = B2.create();
+      }
+      test(new A(), b1, b2);
+    }
+
+    static boolean alwaysFalse() {
+      return false;
+    }
+
+    @NeverInline
+    private static void test(A a, B1 b1, B2 b2) {
+      System.out.print(a.m("Hello"));
+      if (b1 != null) {
+        System.out.print(b1.virtualBridge(" "));
+      }
+      if (b2 != null) {
+        System.out.println(b2.virtualBridge("world!"));
+      }
+    }
+  }
+
+  static class A {
+
+    @NeverInline
+    public Object m(String arg) {
+      return System.currentTimeMillis() >= 0 ? arg : null;
+    }
+  }
+
+  @NeverClassInline
+  static class B1 extends A {
+
+    static B1 create() {
+      return new B1();
+    }
+
+    @NeverInline
+    public /*bridge*/ String virtualBridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+
+  @NeverClassInline
+  static class B2 extends A {
+
+    static B2 create() {
+      return new B2();
+    }
+
+    @NeverInline
+    public /*bridge*/ String virtualBridge(Object o) {
+      return (String) m((String) o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
index 68baadf..e7c5ce1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.google.common.collect.ImmutableList;
@@ -76,7 +76,7 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeL = buildType(L.class, appView.dexItemFactory());
     DexType typeA = buildType(A.class, appView.dexItemFactory());
@@ -117,7 +117,7 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeL = buildType(L.class, appView.dexItemFactory());
     DexType typeA = buildType(A.class, appView.dexItemFactory());
@@ -157,7 +157,7 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, J.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
@@ -194,7 +194,7 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, A.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
@@ -233,7 +233,7 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    ResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index 7b5437e..0ea8597 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -14,10 +14,6 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.A;
-import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.B;
-import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.I;
-import com.android.tools.r8.classmerging.horizontal.dispatch.OverrideDefaultMethodTest.J;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import org.junit.Test;
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
index 6e93ac6..b42cd2e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInitTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -54,6 +55,8 @@
               // The initializer is small in this example so only allow FORCE inlining.
               options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
             })
+        .addVerticallyMergedClassesInspector(
+            VerticallyMergedClassesInspector::assertNoClassesMerged)
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 4b72ec5..b79721f 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -75,7 +75,6 @@
             builder,
             testBuilder ->
                 testBuilder
-                    .addKeepMainRule(CLASS_NAME)
                     // Add main dex rule to disable Class.forName() optimization.
                     .addMainDexRules("-keep class " + CLASS_NAME)
                     .addDontOptimize()
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
new file mode 100644
index 0000000..ee5142f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -0,0 +1,238 @@
+// 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.desugar.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class JacocoConstantDynamicTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean useConstantDynamic;
+
+  @Parameters(name = "{0}, useConstantDynamic: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
+  }
+
+  public static JacocoClasses testClassesNoConstantDynamic;
+  public static JacocoClasses testClassesConstantDynamic;
+
+  public JacocoClasses testClasses;
+
+  private static final String MAIN_CLASS = TestRunner.class.getTypeName();
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+  @BeforeClass
+  public static void setUpInput() throws IOException {
+    testClassesNoConstantDynamic = testClasses(getStaticTemp(), CfVersion.V1_8);
+    testClassesConstantDynamic = testClasses(getStaticTemp(), CfVersion.V11);
+  }
+
+  @Before
+  public void setUp() throws IOException {
+    testClasses = useConstantDynamic ? testClassesConstantDynamic : testClassesNoConstantDynamic;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(
+        parameters.isCfRuntime()
+            && (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)
+                || !useConstantDynamic));
+
+    // Run non-instrumented code.
+    testForRuntime(parameters)
+        .addProgramFiles(testClasses.getOriginal())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+
+    // Run instrumented code without an agent.
+    testForRuntime(parameters)
+        .addProgramFiles(testClasses.getInstrumented())
+        .addProgramFiles(ToolHelper.JACOCO_AGENT)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+
+    // Run non-instrumented code with an agent causing on the fly instrumentation on the JVM.
+    Path output = temp.newFolder().toPath();
+    Path agentOutputOnTheFly = output.resolve("on-the-fly");
+    testForJvm()
+        .addProgramFiles(testClasses.getOriginal())
+        .enableJaCoCoAgent(ToolHelper.JACOCO_AGENT, agentOutputOnTheFly)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    List<String> onTheFlyReport = testClasses.generateReport(agentOutputOnTheFly);
+    assertEquals(2, onTheFlyReport.size());
+
+    // Run the instrumented code with offline instrumentation turned on.
+    Path agentOutputOffline = output.resolve("offline");
+    testForJvm()
+        .addProgramFiles(testClasses.getInstrumented())
+        .enableJaCoCoAgentForOfflineInstrumentedCode(ToolHelper.JACOCO_AGENT, agentOutputOffline)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    List<String> offlineReport = testClasses.generateReport(agentOutputOffline);
+    assertEquals(onTheFlyReport, offlineReport);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.getRuntime().isDex());
+    if (!useConstantDynamic) {
+      Path output = temp.newFolder().toPath();
+      Path agentOutput = output.resolve("jacoco.exec");
+      testForD8(parameters.getBackend())
+          .addProgramFiles(testClasses.getInstrumented())
+          .addProgramFiles(ToolHelper.JACOCO_AGENT)
+          .setMinApi(parameters.getApiLevel())
+          .compile()
+          .runWithJaCoCo(agentOutput, parameters.getRuntime(), MAIN_CLASS)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+      // TODO(sgjesse): Need to figure out why there is no instrumentation output for newer VMs.
+      if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+        List<String> report = testClasses.generateReport(agentOutput);
+        assertEquals(2, report.size());
+      } else {
+        assertFalse(Files.exists(agentOutput));
+      }
+    } else {
+      assertThrows(
+          CompilationFailedException.class,
+          () ->
+              testForD8(parameters.getBackend())
+                  .addProgramFiles(testClasses.getInstrumented())
+                  .addProgramFiles(ToolHelper.JACOCO_AGENT)
+                  .setMinApi(parameters.getApiLevel())
+                  .compileWithExpectedDiagnostics(
+                      diagnostics -> {
+                        // Check that the error is reported as an error to the diagnostics handler.
+                        diagnostics.assertErrorsCount(1);
+                        diagnostics.assertAllErrorsMatch(
+                            allOf(
+                                diagnosticMessage(containsString("Unsupported dynamic constant")),
+                                // The fatal error is not given an origin, so it can't provide it.
+                                // Note: This could be fixed by delaying reporting and associate the
+                                // info
+                                //  at the top-level handler. It would require mangling of the
+                                // diagnostic,
+                                //  so maybe not that elegant.
+                                diagnosticOrigin(Origin.unknown())));
+                        diagnostics.assertWarningsCount(0);
+                        diagnostics.assertInfosCount(0);
+                      }));
+    }
+  }
+
+  private static JacocoClasses testClasses(TemporaryFolder temp, CfVersion version)
+      throws IOException {
+    return new JacocoClasses(
+        transformer(TestRunner.class)
+            .setVersion(version) /*.setClassDescriptor("LTestRunner;")*/
+            .transform(),
+        temp);
+  }
+
+  // Two sets of class files with and without JaCoCo off line instrumentation.
+  private static class JacocoClasses {
+    private final Path dir;
+
+    private final Path originalJar;
+    private final Path instrumentedJar;
+
+    // Create JacocoClasses with just one class provided as bytes.
+    private JacocoClasses(byte[] clazz, TemporaryFolder temp) throws IOException {
+      dir = temp.newFolder().toPath();
+
+      // Write the class to a .class file with package sub-directories.
+      String typeName = extractClassName(clazz);
+      int lastDotIndex = typeName.lastIndexOf('.');
+      String pkg = typeName.substring(0, lastDotIndex);
+      String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
+      Path original = dir.resolve("original");
+      Files.createDirectories(original);
+      Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar));
+      Files.createDirectories(packageDir);
+      Path classFile = packageDir.resolve(baseFileName);
+      Files.write(classFile, clazz);
+
+      // Run offline instrumentation.
+      Path instrumented = dir.resolve("instrumented");
+      Files.createDirectories(instrumented);
+      runJacocoInstrumentation(original, instrumented);
+      originalJar = dir.resolve("original" + JAR_EXTENSION);
+      ZipUtils.zip(originalJar, original);
+      instrumentedJar = dir.resolve("instrumented" + JAR_EXTENSION);
+      ZipUtils.zip(instrumentedJar, instrumented);
+    }
+
+    public Path getOriginal() {
+      return originalJar;
+    }
+
+    public Path getInstrumented() {
+      return instrumentedJar;
+    }
+
+    public List<String> generateReport(Path jacocoExec) throws IOException {
+      Path report = dir.resolve("report.scv");
+      ProcessResult result = ToolHelper.runJaCoCoReport(originalJar, jacocoExec, report);
+      assertEquals(result.toString(), 0, result.exitCode);
+      return Files.readAllLines(report);
+    }
+
+    private void runJacocoInstrumentation(Path input, Path outdir) throws IOException {
+      ProcessResult result = ToolHelper.runJaCoCoInstrument(input, outdir);
+      assertEquals(result.toString(), 0, result.exitCode);
+    }
+  }
+
+  static class TestRunner {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index ddebca5..fdca331 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -126,7 +126,7 @@
       Path desugaredLib =
           getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
 
-      // Run on the JVM with desuagred library on classpath.
+      // Run on the JVM with desugared library on classpath.
       testForJvm()
           .addProgramFiles(jar)
           .addRunClasspathFiles(desugaredLib)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
index 1f4d791..a23a6d8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideInLibraryTest.java
@@ -37,7 +37,7 @@
 @RunWith(Parameterized.class)
 public class DefaultMethodOverrideInLibraryTest extends DesugaredLibraryTestBase {
 
-  static final String EXPECTED = StringUtils.lines("0", "42");
+  static final String EXPECTED = StringUtils.lines("0", "42", "0", "0", "42", "42");
 
   private final TestParameters parameters;
 
@@ -83,7 +83,7 @@
     if (parameters.isDexRuntime()
         && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
         && parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V7_0_0)) {
-      result.assertSuccessWithOutputLines("42", "42");
+      result.assertSuccessWithOutputLines("42", "42", "0", "0", "42", "42");
       return;
     }
     result.assertSuccessWithOutput(EXPECTED);
@@ -127,16 +127,32 @@
   static class MyIntegerArrayListWithoutOverride extends ArrayList<Integer>
       implements MyIntegerList {
     // No override of spliterator.
+
+    public Spliterator<Integer> superSpliteratorItf() {
+      return MyIntegerList.super.spliterator();
+    }
+
+    public Spliterator<Integer> superSpliterator() {
+      return super.spliterator();
+    }
   }
 
   // Derived list with an override of spliterator. The call must hit the classes override and that
-  // will explictly call the custom default method.
+  // will explicitly call the custom default method.
   static class MyIntegerArrayListWithOverride extends ArrayList<Integer> implements MyIntegerList {
 
     @Override
     public Spliterator<Integer> spliterator() {
       return MyIntegerList.super.spliterator();
     }
+
+    public Spliterator<Integer> superSpliteratorItf() {
+      return MyIntegerList.super.spliterator();
+    }
+
+    public Spliterator<Integer> superSpliterator() {
+      return super.spliterator();
+    }
   }
 
   static class Main {
@@ -144,6 +160,11 @@
     public static void main(String[] args) {
       System.out.println(new MyIntegerArrayListWithoutOverride().spliterator().estimateSize());
       System.out.println(new MyIntegerArrayListWithOverride().spliterator().estimateSize());
+      System.out.println(new MyIntegerArrayListWithoutOverride().superSpliterator().estimateSize());
+      System.out.println(new MyIntegerArrayListWithOverride().superSpliterator().estimateSize());
+      System.out.println(
+          new MyIntegerArrayListWithoutOverride().superSpliteratorItf().estimateSize());
+      System.out.println(new MyIntegerArrayListWithOverride().superSpliteratorItf().estimateSize());
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FreezePeriodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FreezePeriodTest.java
new file mode 100644
index 0000000..ac48549
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FreezePeriodTest.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.MonthDay;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FreezePeriodTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.O;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "FP:--05-05;--06-06",
+          "FP:--05-05;--06-06",
+          "FP:--05-05;--06-0601",
+          "MFP:--05-05;--06-06");
+  private static Path CUSTOM_LIB;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public FreezePeriodTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB =
+        testForD8(getStaticTemp())
+            .addProgramClasses(FreezePeriod.class)
+            .setMinApi(MIN_SUPPORTED)
+            .compile()
+            .writeToZip();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class, MyFreezePeriod.class)
+        .addLibraryClasses(FreezePeriod.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8CfToCf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path jar =
+        testForD8(Backend.CF)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClasses(Executor.class, MyFreezePeriod.class)
+            .addLibraryClasses(FreezePeriod.class)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .addRunClasspathFiles(CUSTOM_LIB)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .addRunClasspathFiles(CUSTOM_LIB)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Executor.class)
+        .addProgramClasses(Executor.class, MyFreezePeriod.class)
+        .addLibraryClasses(FreezePeriod.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .allowStdoutMessages()
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .addRunClasspathFiles(CUSTOM_LIB)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      testConversion2ArgsOrLess();
+      testConversion3ArgsOrMore();
+      testConversionWithValuesOnStack();
+      testConversionSuperInit();
+    }
+
+    private static void testConversionSuperInit() {
+      MyFreezePeriod myFreezePeriod = new MyFreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6));
+      System.out.println(myFreezePeriod.print());
+    }
+
+    private static void testConversionWithValuesOnStack() {
+      print(0, new FreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6)), 1);
+    }
+
+    private static void testConversion3ArgsOrMore() {
+      FreezePeriod freezePeriod2 = new FreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6), 0, 1);
+      System.out.println(freezePeriod2.print());
+    }
+
+    private static void testConversion2ArgsOrLess() {
+      FreezePeriod freezePeriod = new FreezePeriod(MonthDay.of(5, 5), MonthDay.of(6, 6));
+      System.out.println(freezePeriod.print());
+    }
+
+    static void print(int i1, FreezePeriod freezePeriod, int i2) {
+      System.out.println(freezePeriod.print() + i1 + i2);
+    }
+  }
+
+  static class MyFreezePeriod extends FreezePeriod {
+
+    public MyFreezePeriod(MonthDay start, MonthDay end) {
+      super(start, end);
+    }
+
+    public String print() {
+      return "M" + super.print();
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class FreezePeriod {
+
+    private final MonthDay start;
+    private final MonthDay end;
+
+    public FreezePeriod(MonthDay start, MonthDay end) {
+      this.start = start;
+      this.end = end;
+    }
+
+    public FreezePeriod(MonthDay start, MonthDay end, int extra1, Integer extra2) {
+      this.start = start;
+      this.end = end;
+    }
+
+    public String print() {
+      return "FP:" + start + ";" + end;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index 3409cfd..e1592c0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
 import java.time.Instant;
 import java.time.ZonedDateTime;
 import java.util.Date;
@@ -15,6 +16,7 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.IntUnaryOperator;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -29,7 +31,8 @@
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
   public RetargetOverrideTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
@@ -38,7 +41,65 @@
   }
 
   @Test
+  public void testRetargetOverrideCf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path desugaredTwice =
+        testForD8(Backend.CF)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addProgramFiles(
+                testForD8(Backend.CF)
+                    .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                    .addInnerClasses(RetargetOverrideTest.class)
+                    .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+                    .setMinApi(parameters.getApiLevel())
+                    .compile()
+                    .writeToZip())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(
+                options -> options.desugarSpecificOptions().allowAllDesugaredInput = true)
+            .compile()
+            .writeToZip();
+
+    String stdout;
+    if (parameters.getRuntime().isDex()) {
+      // Convert to DEX without desugaring and run.
+      stdout =
+          testForD8(Backend.DEX)
+              .addProgramFiles(desugaredTwice)
+              .setMinApi(parameters.getApiLevel())
+              .disableDesugaring()
+              .compile()
+              .addDesugaredCoreLibraryRunClassPath(
+                  this::buildDesugaredLibrary,
+                  parameters.getApiLevel(),
+                  keepRuleConsumer.get(),
+                  shrinkDesugaredLibrary)
+              .run(
+                  parameters.getRuntime(),
+                  Executor.class,
+                  Boolean.toString(parameters.getRuntime().isCf()))
+              .assertSuccess()
+              .getStdOut();
+    } else {
+      // Run on the JVM with desugared library on classpath.
+      stdout =
+          testForJvm()
+              .addProgramFiles(desugaredTwice)
+              .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
+              .run(
+                  parameters.getRuntime(),
+                  Executor.class,
+                  Boolean.toString(parameters.getRuntime().isCf()))
+              .assertSuccess()
+              .getStdOut();
+    }
+    assertLines2By2Correct(stdout);
+  }
+
+  @Test
   public void testRetargetOverrideD8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForD8()
@@ -52,7 +113,10 @@
                 parameters.getApiLevel(),
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
-            .run(parameters.getRuntime(), Executor.class)
+            .run(
+                parameters.getRuntime(),
+                Executor.class,
+                Boolean.toString(parameters.getRuntime().isCf()))
             .assertSuccess()
             .getStdOut();
     assertLines2By2Correct(stdout);
@@ -60,6 +124,7 @@
 
   @Test
   public void testRetargetOverrideR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String stdout =
         testForR8(Backend.DEX)
@@ -74,7 +139,10 @@
                 parameters.getApiLevel(),
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
-            .run(parameters.getRuntime(), Executor.class)
+            .run(
+                parameters.getRuntime(),
+                Executor.class,
+                Boolean.toString(parameters.getRuntime().isCf()))
             .assertSuccess()
             .getStdOut();
     assertLines2By2Correct(stdout);
@@ -83,27 +151,32 @@
   static class Executor {
 
     public static void main(String[] args) {
-      directTypes();
-      polyTypes();
-      baseTypes();
+      boolean isJvm = Boolean.parseBoolean(args[0]);
+      directTypes(isJvm);
+      polyTypes(isJvm);
+      baseTypes(isJvm);
     }
 
-    public static void directTypes() {
+    public static void directTypes(boolean isJvm) {
       MyCalendarOverride myCal = new MyCalendarOverride(1990, 2, 22);
-      System.out.println(myCal.toZonedDateTime());
-      System.out.println("1990-11-22T00:00Z[GMT]");
-      System.out.println(myCal.toInstant());
-      System.out.println("1990-03-22T00:00:00Z");
+      if (!isJvm) {
+        System.out.println(myCal.toZonedDateTime());
+        System.out.println("1990-11-22T00:00Z[GMT]");
+        System.out.println(myCal.toInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+      }
 
       MyCalendarNoOverride myCalN = new MyCalendarNoOverride(1990, 2, 22);
-      System.out.println(myCalN.toZonedDateTime());
-      System.out.println("1990-03-22T00:00Z[GMT]");
-      System.out.println(myCalN.superToZonedDateTime());
-      System.out.println("1990-03-22T00:00Z[GMT]");
-      System.out.println(myCalN.toInstant());
-      System.out.println("1990-03-22T00:00:00Z");
-      System.out.println(myCalN.superToInstant());
-      System.out.println("1990-03-22T00:00:00Z");
+      if (!isJvm) {
+        System.out.println(myCalN.toZonedDateTime());
+        System.out.println("1990-03-22T00:00Z[GMT]");
+        System.out.println(myCalN.superToZonedDateTime());
+        System.out.println("1990-03-22T00:00Z[GMT]");
+        System.out.println(myCalN.toInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+        System.out.println(myCalN.superToInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+      }
 
       MyDateDoubleOverride myDateCast2 = new MyDateDoubleOverride(123456789);
       System.out.println(myDateCast2.toInstant());
@@ -128,12 +201,13 @@
       System.out.println("145");
 
       Date date1 = MyDateNoOverride.from(myCal.toInstant());
-      System.out.println(date1.toInstant());
-      System.out.println("1990-03-22T00:00:00Z");
-      Date date2 = MyDateOverride.from(myCal.toInstant());
-      System.out.println(date2.toInstant());
-      System.out.println("1990-03-22T00:00:00Z");
-
+      if (!isJvm) {
+        System.out.println(date1.toInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+        Date date2 = MyDateOverride.from(myCal.toInstant());
+        System.out.println(date2.toInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+      }
       System.out.println(MyDateDoubleOverride.from(myCal.toInstant()).toInstant());
       System.out.println("1970-01-02T10:17:36.788Z");
 
@@ -141,7 +215,7 @@
       System.out.println("1970-01-02T10:17:36.788Z");
     }
 
-    public static void polyTypes() {
+    public static void polyTypes(boolean isJvm) {
       Date myDateCast = new MyDateOverride(123456789);
       System.out.println(myDateCast.toInstant());
       System.out.println("1970-01-02T10:17:45.789Z");
@@ -155,27 +229,35 @@
       System.out.println("1970-01-02T10:17:36.789Z");
 
       GregorianCalendar myCalCast = new MyCalendarOverride(1990, 2, 22);
-      System.out.println(myCalCast.toZonedDateTime());
-      System.out.println("1990-11-22T00:00Z[GMT]");
-      System.out.println(myCalCast.toInstant());
-      System.out.println("1990-03-22T00:00:00Z");
+      if (!isJvm) {
+        System.out.println(myCalCast.toZonedDateTime());
+        System.out.println("1990-11-22T00:00Z[GMT]");
+        System.out.println(myCalCast.toInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+      }
 
       GregorianCalendar myCalN = new MyCalendarNoOverride(1990, 2, 22);
-      System.out.println(myCalN.toZonedDateTime());
-      System.out.println("1990-03-22T00:00Z[GMT]");
-      System.out.println(myCalN.toInstant());
-      System.out.println("1990-03-22T00:00:00Z");
+      if (!isJvm) {
+        System.out.println(myCalN.toZonedDateTime());
+        System.out.println("1990-03-22T00:00Z[GMT]");
+        System.out.println(myCalN.toInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+      }
     }
 
-    public static void baseTypes() {
+    public static void baseTypes(boolean isJvm) {
       java.sql.Date date = new java.sql.Date(123456789);
-      // The following one is not working on JVMs, but works on Android...
-      System.out.println(date.toInstant());
-      System.out.println("1970-01-02T10:17:36.789Z");
+      if (!isJvm) {
+        // The following one is not working on JVMs, but works on Android...
+        System.out.println(date.toInstant());
+        System.out.println("1970-01-02T10:17:36.789Z");
+      }
 
       GregorianCalendar gregCal = new GregorianCalendar(1990, 2, 22);
-      System.out.println(gregCal.toInstant());
-      System.out.println("1990-03-22T00:00:00Z");
+      if (!isJvm) {
+        System.out.println(gregCal.toInstant());
+        System.out.println("1990-03-22T00:00:00Z");
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 9f9dd85..3f0992a 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -34,10 +34,10 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withCustomRuntime(CfRuntime.getCheckedInJdk16())
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
index e936a33..7064af6 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
@@ -37,9 +37,9 @@
 
   @Parameters(name = "{0} back: {1}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build(),
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk16()).build(),
         Backend.values());
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index d388ac4..c6b3f68 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -37,7 +37,7 @@
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withCustomRuntime(CfRuntime.getCheckedInJdk16())
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 711909f..55cb175 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -47,10 +47,10 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withCustomRuntime(CfRuntime.getCheckedInJdk16())
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index bd08a25..240e128 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -40,10 +40,10 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withCustomRuntime(CfRuntime.getCheckedInJdk16())
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index 15684fe..9663f43 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -43,9 +43,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk16()).build());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
index 7589764..86e30a6 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -31,7 +31,7 @@
  */
 public class RecordTestUtils {
 
-  private static final String EXAMPLE_FOLDER = "examplesJava15";
+  private static final String EXAMPLE_FOLDER = "examplesJava16";
   private static final String RECORD_FOLDER = "records";
 
   public static Path jar() {
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index 5472f22..c36375e 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -36,10 +36,10 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withCustomRuntime(CfRuntime.getCheckedInJdk16())
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
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 4820c08..51596a1 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
@@ -35,10 +35,10 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withCustomRuntime(CfRuntime.getCheckedInJdk16())
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
index ee2e4e1..d311db8 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.examples.jdk15.Sealed;
+import com.android.tools.r8.examples.jdk16.Sealed;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.List;
 import org.junit.Test;
@@ -28,9 +28,9 @@
 
   @Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
-        getTestParameters().withCustomRuntime(TestRuntime.getCheckedInJdk15()).build(),
+        getTestParameters().withCustomRuntime(TestRuntime.getCheckedInJdk16()).build(),
         Backend.values());
   }
 
@@ -44,7 +44,7 @@
     testForJvm()
         .addRunClasspathFiles(Sealed.jar())
         .enablePreview()
-        .run(TestRuntime.getCheckedInJdk15(), Sealed.Main.typeName())
+        .run(TestRuntime.getCheckedInJdk16(), Sealed.Main.typeName())
         .assertSuccessWithOutputLines("R8 compiler", "D8 compiler");
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InlineIntoReprocessedStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InlineIntoReprocessedStaticInterfaceMethodTest.java
new file mode 100644
index 0000000..fbc6474
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InlineIntoReprocessedStaticInterfaceMethodTest.java
@@ -0,0 +1,83 @@
+// 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.desugar.staticinterfacemethod;
+
+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 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;
+
+/** Reproduction for b/196496374. */
+@RunWith(Parameterized.class)
+public class InlineIntoReprocessedStaticInterfaceMethodTest 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()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class Main {
+
+    // Field where all writes are removed by the primary optimization pass. All methods that read
+    // this field will therefore be enqueued for reprocessing in the second optimization pass.
+    public static boolean neverWritten;
+
+    public static void main(String[] args) {
+      if (getFalse()) {
+        neverWritten = args.length > 0;
+      }
+      I.method();
+    }
+
+    static boolean getFalse() {
+      return false;
+    }
+  }
+
+  static class A {
+
+    static void greet() {
+      System.out.println("A");
+    }
+  }
+
+  interface I {
+
+    // Since this method is moved to I's companion class during the primary optimization pass, we
+    // will process the companion method in the second optimization pass, since the body references
+    // the `neverWritten` field.
+    @NeverInline
+    static void method() {
+      if (Main.neverWritten) {
+        // This will be inlined. When building IR for this method in the second optimization pass,
+        // we assert that the outermost caller position is the original signature of the enclosing
+        // method. This only holds if we have correctly recorded that the original signature of the
+        // companion method I$-CC.method() is I.method().
+        System.out.println("Dead...");
+      }
+      A.greet();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/examples/jdk16/PatternMatchingForInstenceof.java b/src/test/java/com/android/tools/r8/examples/jdk16/PatternMatchingForInstenceof.java
new file mode 100644
index 0000000..0cc4846
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/jdk16/PatternMatchingForInstenceof.java
@@ -0,0 +1,20 @@
+// 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.examples.jdk16;
+
+import com.android.tools.r8.examples.JavaExampleClassProxy;
+import java.nio.file.Path;
+
+public class PatternMatchingForInstenceof {
+
+  private static final String EXAMPLE_FILE = "examplesJava16/pattern_matching_for_instanceof";
+
+  public static final JavaExampleClassProxy Main =
+      new JavaExampleClassProxy(EXAMPLE_FILE, "pattern_matching_for_instanceof/Main");
+
+  public static Path jar() {
+    return JavaExampleClassProxy.examplesJar(EXAMPLE_FILE);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/examples/jdk15/Records.java b/src/test/java/com/android/tools/r8/examples/jdk16/Records.java
similarity index 85%
rename from src/test/java/com/android/tools/r8/examples/jdk15/Records.java
rename to src/test/java/com/android/tools/r8/examples/jdk16/Records.java
index 9eafc56..a8677b8 100644
--- a/src/test/java/com/android/tools/r8/examples/jdk15/Records.java
+++ b/src/test/java/com/android/tools/r8/examples/jdk16/Records.java
@@ -2,14 +2,14 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.examples.jdk15;
+package com.android.tools.r8.examples.jdk16;
 
 import com.android.tools.r8.examples.JavaExampleClassProxy;
 import java.nio.file.Path;
 
 public class Records {
 
-  private static final String EXAMPLE_FILE = "examplesJava15/records";
+  private static final String EXAMPLE_FILE = "examplesJava16/records";
 
   public static final JavaExampleClassProxy Main =
       new JavaExampleClassProxy(EXAMPLE_FILE, "records/Main");
diff --git a/src/test/java/com/android/tools/r8/examples/jdk15/Sealed.java b/src/test/java/com/android/tools/r8/examples/jdk16/Sealed.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/examples/jdk15/Sealed.java
rename to src/test/java/com/android/tools/r8/examples/jdk16/Sealed.java
index 1450ee8..c995d13 100644
--- a/src/test/java/com/android/tools/r8/examples/jdk15/Sealed.java
+++ b/src/test/java/com/android/tools/r8/examples/jdk16/Sealed.java
@@ -2,14 +2,14 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.examples.jdk15;
+package com.android.tools.r8.examples.jdk16;
 
 import com.android.tools.r8.examples.JavaExampleClassProxy;
 import java.nio.file.Path;
 
 public class Sealed {
 
-  private static final String EXAMPLE_FILE = "examplesJava15/sealed";
+  private static final String EXAMPLE_FILE = "examplesJava16/sealed";
 
   public static final JavaExampleClassProxy Compiler =
       new JavaExampleClassProxy(EXAMPLE_FILE, "sealed/Compiler");
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
index a040b37..da89df6 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -123,7 +123,7 @@
                         HashMap::new,
                         s -> s,
                         ClassTypeSignature::new,
-                        (val1, val2) -> {
+                        (key, val1, val2) -> {
                           throw new Unreachable("No keys should be merged");
                         }))
                 .addLiveParameters(liveVariables),
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 57078d1..8538449 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
@@ -75,7 +75,7 @@
         appInfo().resolveMethodOnClass(method.getReference(), id.holder).getSingleTarget(), method);
 
     // Check lookup targets with include method.
-    ResolutionResult resolutionResult =
+    MethodResolutionResult resolutionResult =
         appInfo().resolveMethodOnClass(method.getReference(), clazz);
     AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
     LookupResult lookupResult =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index 5dd3140..5162100 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
@@ -61,15 +62,14 @@
   public void testWriteOnlyField_dontoptimize() throws Exception {
     CodeInspector inspector = runR8(DONT_OPTIMIZE);
     ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
-    clazz.forAllMethods(
-        methodSubject -> {
-          // Dead code removal is not part of -dontoptimize. That is, even with -dontoptimize,
-          // field put instructions are gone with better dead code removal.
-          assertTrue(
-              methodSubject
-                  .streamInstructions()
-                  .noneMatch(i -> i.isInstancePut() || i.isStaticPut()));
-        });
+    // With the support of 'allowshrinking' dontoptimize will effectivelys pin all
+    // items that are not tree shaken out. The field operations will thus remain.
+    assertTrue(clazz.clinit().streamInstructions().anyMatch(InstructionSubject::isStaticPut));
+    assertTrue(
+        clazz
+            .uniqueInstanceInitializer()
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isInstancePut));
   }
 
   private CodeInspector runR8(Path proguardConfig) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java
index 774267d..d53a470 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationLibraryLambdaPropagationTest.java
@@ -8,8 +8,9 @@
 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.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
 import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -19,18 +20,23 @@
 @RunWith(Parameterized.class)
 public class CallSiteOptimizationLibraryLambdaPropagationTest extends TestBase {
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimes()
-        .withDexRuntimes()
-        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
-        .build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withCfRuntimes()
+            .withDexRuntimes()
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+            .build());
   }
 
-  public CallSiteOptimizationLibraryLambdaPropagationTest(TestParameters parameters) {
+  public CallSiteOptimizationLibraryLambdaPropagationTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -39,6 +45,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CallSiteOptimizationLibraryLambdaPropagationTest.class)
         .addKeepMainRule(TestClass.class)
+        .applyIf(
+            enableExperimentalArgumentPropagation,
+            builder ->
+                builder.addOptionsModification(
+                    options ->
+                        options
+                            .callSiteOptimizationOptions()
+                            .setEnableExperimentalArgumentPropagation()))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
index 9e42eb6..2bf8f8d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
@@ -11,9 +11,9 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -24,14 +24,19 @@
 
   private static final String CLASS_PREFIX =
       "com.android.tools.r8.ir.optimize.callsites.CallSiteOptimizationPinnedMethodOverridePropagationTest$";
+
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
-  public CallSiteOptimizationPinnedMethodOverridePropagationTest(TestParameters parameters) {
+  public CallSiteOptimizationPinnedMethodOverridePropagationTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -58,13 +63,20 @@
                         + "Arg getArg2(); \npublic static "
                         + CLASS_PREFIX
                         + "Call getCaller(); \n}"))
+            .applyIf(
+                enableExperimentalArgumentPropagation,
+                builder ->
+                    builder.addOptionsModification(
+                        options ->
+                            options
+                                .callSiteOptimizationOptions()
+                                .setEnableExperimentalArgumentPropagation()))
             .enableNoVerticalClassMergingAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
             .enableInliningAnnotations()
             .enableMemberValuePropagationAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile();
-    CodeInspector inspector = compiled.inspector();
     compiled.run(parameters.getRuntime(), Main2.class).assertSuccessWithOutputLines("Arg1");
     testForD8()
         .addProgramClasses(Main.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java
index 86145c6..6d8e9d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationProgramLambdaPropagationTest.java
@@ -8,7 +8,8 @@
 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.BooleanUtils;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -17,14 +18,18 @@
 @RunWith(Parameterized.class)
 public class CallSiteOptimizationProgramLambdaPropagationTest extends TestBase {
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public CallSiteOptimizationProgramLambdaPropagationTest(TestParameters parameters) {
+  public CallSiteOptimizationProgramLambdaPropagationTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -33,6 +38,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CallSiteOptimizationProgramLambdaPropagationTest.class)
         .addKeepMainRule(TestClass.class)
+        .applyIf(
+            enableExperimentalArgumentPropagation,
+            builder ->
+                builder.addOptionsModification(
+                    options ->
+                        options
+                            .callSiteOptimizationOptions()
+                            .setEnableExperimentalArgumentPropagation()))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
index 1a2d667..d0d2d22 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
@@ -12,7 +12,7 @@
 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.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableList;
@@ -33,18 +33,23 @@
 
   private static final String EXPECTED = StringUtils.lines("Hello world!");
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withAllRuntimes()
-        // Only works when invoke-custom/dynamic are supported and ConstantCallSite defined.
-        .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
-        .build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withAllRuntimes()
+            // Only works when invoke-custom/dynamic are supported and ConstantCallSite defined.
+            .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
+            .build());
   }
 
-  public CallSiteOptimizationWithInvokeCustomTargetTest(TestParameters parameters) {
+  public CallSiteOptimizationWithInvokeCustomTargetTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -61,9 +66,17 @@
     testForR8(parameters.getBackend())
         .addProgramClassFileData(getProgramClassFileData())
         .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters.getApiLevel())
         .addKeepMethodRules(methodFromMethod(TestClass.class.getDeclaredMethod("bar", int.class)))
+        .applyIf(
+            enableExperimentalArgumentPropagation,
+            builder ->
+                builder.addOptionsModification(
+                    options ->
+                        options
+                            .callSiteOptimizationOptions()
+                            .setEnableExperimentalArgumentPropagation()))
         .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
index c04f9b6..4db8c2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
@@ -8,7 +8,8 @@
 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.BooleanUtils;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -17,14 +18,18 @@
 @RunWith(Parameterized.class)
 public class CallSiteOptimizationWithLambdaTargetTest extends TestBase {
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public CallSiteOptimizationWithLambdaTargetTest(TestParameters parameters) {
+  public CallSiteOptimizationWithLambdaTargetTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -33,6 +38,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CallSiteOptimizationWithLambdaTargetTest.class)
         .addKeepMainRule(TestClass.class)
+        .applyIf(
+            enableExperimentalArgumentPropagation,
+            builder ->
+                builder.addOptionsModification(
+                    options ->
+                        options
+                            .callSiteOptimizationOptions()
+                            .setEnableExperimentalArgumentPropagation()))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
index 8d405c4..b579686 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
@@ -11,29 +11,34 @@
 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.graph.ProgramMethod;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class KeptMethodTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public KeptMethodTest(TestParameters parameters) {
+  public KeptMethodTest(boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -43,12 +48,19 @@
         .addInnerClasses(KeptMethodTest.class)
         .addKeepMainRule(MAIN)
         .addKeepClassAndMembersRules(A.class)
+        .applyIf(
+            enableExperimentalArgumentPropagation,
+            builder ->
+                builder.addOptionsModification(
+                    options ->
+                        options
+                            .callSiteOptimizationOptions()
+                            .setEnableExperimentalArgumentPropagation()))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
-            o -> {
-              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-            })
+            o ->
+                o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null", "non-null")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
index 8a057dd..ea7641f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
@@ -11,13 +11,14 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import java.util.function.Predicate;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,18 +28,23 @@
 public class LibraryMethodOverridesTest extends TestBase {
   private static final Class<?> MAIN = TestClass.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimes()
-        // java.util.function.Predicate is not available prior to API level 24 (V7.0).
-        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
-        .build();
+  @Parameterized.Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withCfRuntimes()
+            // java.util.function.Predicate is not available prior to API level 24 (V7.0).
+            .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+            .build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public LibraryMethodOverridesTest(TestParameters parameters) {
+  public LibraryMethodOverridesTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -54,10 +60,18 @@
         .addProgramClasses(TestClass.class, CustomPredicate.class)
         .addClasspathClasses(LibClass.class)
         .addKeepMainRule(MAIN)
+        .addOptionsModification(
+            o ->
+                o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect)
+        .applyIf(
+            enableExperimentalArgumentPropagation,
+            builder ->
+                builder.addOptionsModification(
+                    options ->
+                        options
+                            .callSiteOptimizationOptions()
+                            .setEnableExperimentalArgumentPropagation()))
         .enableInliningAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
         .setMinApi(parameters.getRuntime())
         .compile()
         .addRunClasspathFiles(libraryCompileResult.writeToZip())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/InstanceOfSpecializedMethodClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/InstanceOfSpecializedMethodClassInlinerTest.java
new file mode 100644
index 0000000..125f3cd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/InstanceOfSpecializedMethodClassInlinerTest.java
@@ -0,0 +1,64 @@
+// 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.classinliner;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/195234829. */
+@RunWith(Parameterized.class)
+public class InstanceOfSpecializedMethodClassInlinerTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection setup() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("false", "true");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A().isB());
+      System.out.println(new B().isB());
+    }
+  }
+
+  static class A {
+
+    @NeverInline
+    boolean isB() {
+      return false;
+    }
+  }
+
+  static class B extends A {
+
+    @NeverInline
+    @Override
+    boolean isB() {
+      return true;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseGetTest.java
index e0a4c6d..aadd5dd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseGetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseGetTest.java
@@ -41,7 +41,17 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getProgramClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "Bar", "Expected NPE");
+  }
+
+  @Test
+  public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClassFileData(getProgramClassFileData())
         .addKeepMainRule(Main.class)
@@ -65,10 +75,12 @@
               assertThat(testNullArgumentMethodSubject, isPresent());
               assertThat(
                   testNullArgumentMethodSubject,
-                  not(invokesMethodWithName("requireNonNullElseGet")));
+                  parameters.getApiLevel().isGreaterThan(AndroidApiLevel.Q)
+                      ? invokesMethodWithName("requireNonNullElseGet")
+                      : not(invokesMethodWithName("requireNonNullElseGet")));
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("Foo", "Bar");
+        .assertSuccessWithOutputLines("Foo", "Bar", "Expected NPE");
   }
 
   private byte[] getProgramClassFileData() throws IOException {
@@ -83,6 +95,12 @@
     public static void main(String[] args) {
       testNonNullArgument();
       testNullArgument();
+      try {
+        testNullArgumentAndNullSupplier();
+        System.out.println("Unexpected");
+      } catch (NullPointerException e) {
+        System.out.println("Expected NPE");
+      }
     }
 
     @NeverInline
@@ -94,6 +112,11 @@
     static void testNullArgument() {
       System.out.println(Mock.requireNonNullElseGet(null, () -> "Bar"));
     }
+
+    @NeverInline
+    static void testNullArgumentAndNullSupplier() {
+      System.out.println(Mock.requireNonNullElseGet(null, () -> null));
+    }
   }
 
   // References to this class are rewritten to java.util.Objects by transformation.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromDefaultInterfaceMethodTest.java
new file mode 100644
index 0000000..9b3dd42
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromDefaultInterfaceMethodTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.outliner;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// Regression test for the issue causing extra bookkeeping in outlining of interface methods.
+// See clean-up issue b/167345026.
+@RunWith(Parameterized.class)
+public class OutlineFromDefaultInterfaceMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public OutlineFromDefaultInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepMethodRules(methodFromMethod(TestClass.class.getDeclaredMethod("getI")))
+        .addOptionsModification(
+            options -> {
+              options.outline.threshold = 2;
+              options.outline.minSize = 2;
+            })
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .noHorizontalClassMergingOfSynthetics()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject interfaceSubject;
+    MethodSubject greetMethodSubject;
+    if (parameters.isCfRuntime()
+        || parameters
+            .getApiLevel()
+            .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport())) {
+      interfaceSubject = inspector.clazz(I.class);
+      greetMethodSubject = interfaceSubject.uniqueMethodWithName("greet");
+    } else {
+      interfaceSubject = inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(I.class));
+      List<FoundMethodSubject> companionMethods = interfaceSubject.allMethods();
+      assertEquals(1, companionMethods.size());
+      greetMethodSubject = companionMethods.get(0);
+    }
+    assertThat(interfaceSubject, isPresent());
+    assertThat(greetMethodSubject, isPresent());
+    assertEquals(
+        1,
+        greetMethodSubject.streamInstructions().filter(InstructionSubject::isInvokeStatic).count());
+  }
+
+  static class TestClass {
+
+    static I getI() {
+      return new I() {};
+    }
+
+    public static void main(String... args) {
+      greet();
+      getI().greet();
+    }
+
+    @NeverInline
+    static void greet() {
+      Greeter.hello();
+      Greeter.world();
+    }
+  }
+
+  interface I {
+
+    @NeverInline
+    default void greet() {
+      Greeter.hello();
+      Greeter.world();
+    }
+  }
+
+  @NeverClassInline
+  public static class Greeter {
+
+    @NeverInline
+    public static void hello() {
+      System.out.print("Hello");
+    }
+
+    @NeverInline
+    public static void world() {
+      System.out.println(" world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
new file mode 100644
index 0000000..345f198
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
@@ -0,0 +1,79 @@
+// 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.java_language.pattern_matching_for_instenceof;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.examples.jdk16.PatternMatchingForInstenceof;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PattternMatchingForInstanceOfTest extends TestBase {
+
+  @Parameter public TestParameters parameters;
+
+  private static List<String> EXPECTED = ImmutableList.of("Hello, world!");
+
+  private static final Path JAR = PatternMatchingForInstenceof.jar();
+  private static final String MAIN = PatternMatchingForInstenceof.Main.typeName();
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+    return buildParameters(
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+            .withDexRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .build());
+  }
+
+  @Test
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addRunClasspathFiles(JAR)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN)
+          .assertSuccessWithOutputLines(EXPECTED);
+    }
+    testForD8(parameters.getBackend())
+        .addProgramFiles(JAR)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestBuilder<?> builder =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(JAR)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(MAIN)
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+    if (parameters.getBackend().isDex()) {
+      builder.run(parameters.getRuntime(), MAIN).assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      testForJvm()
+          .addRunClasspathFiles(builder.compile().writeToZip())
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN)
+          .assertSuccessWithOutputLines(EXPECTED);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
index 336e9a8..6ba334d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
@@ -53,9 +53,9 @@
             "*E");
     Result result = KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(result);
-    assertEquals(1, result.size());
-    assertEquals(1, (int) result.lookup(1).getKey());
-    Position position = result.lookup(1).getValue();
+    assertEquals(1, result.inlinePositionsCount());
+    assertEquals(1, (int) result.lookupInlinedPosition(1).getKey());
+    Position position = result.lookupInlinedPosition(1).getValue();
     assertEquals("EnumSwitch.kt", position.getSource().getFileName());
     assertEquals("enumswitch/EnumSwitchKt", position.getSource().getPath());
     assertEquals(1, position.getRange().from);
@@ -94,21 +94,21 @@
             "*E");
     Result result = KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(result);
-    assertEquals(3, result.size());
-    assertEquals(1, (int) result.lookup(1).getKey());
-    assertEquals(23, (int) result.lookup(23).getKey());
-    assertEquals(24, (int) result.lookup(24).getKey());
+    assertEquals(3, result.inlinePositionsCount());
+    assertEquals(1, (int) result.lookupInlinedPosition(1).getKey());
+    assertEquals(23, (int) result.lookupInlinedPosition(23).getKey());
+    assertEquals(24, (int) result.lookupInlinedPosition(24).getKey());
 
     // Check that files are correctly parsed.
-    Position pos1 = result.lookup(1).getValue();
+    Position pos1 = result.lookupInlinedPosition(1).getValue();
     assertEquals("Main.kt", pos1.getSource().getFileName());
     assertEquals("retrace/MainKt", pos1.getSource().getPath());
 
-    Position pos2 = result.lookup(23).getValue();
+    Position pos2 = result.lookupInlinedPosition(23).getValue();
     assertEquals("InlineFunction.kt", pos2.getSource().getFileName());
     assertEquals("retrace/InlineFunctionKt", pos2.getSource().getPath());
 
-    Position pos3 = result.lookup(24).getValue();
+    Position pos3 = result.lookupInlinedPosition(24).getValue();
     assertEquals("InlineFunction.kt", pos3.getSource().getFileName());
     assertEquals("retrace/InlineFunction", pos3.getSource().getPath());
 
@@ -315,8 +315,8 @@
             "*E");
     Result parsedResult = KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(parsedResult);
-    assertEquals(24, (int) parsedResult.lookup(25).getKey());
-    Position value = parsedResult.lookup(25).getValue();
+    assertEquals(24, (int) parsedResult.lookupInlinedPosition(25).getKey());
+    Position value = parsedResult.lookupInlinedPosition(25).getValue();
     assertEquals(12, value.getRange().from);
     assertEquals(13, value.getRange().to);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index f8dcce6..baf1308 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
@@ -36,7 +37,14 @@
     this.parameters = parameters;
   }
 
-  private void test(ThrowableConsumer<R8FullTestBuilder> consumer) throws Exception {
+  private void test(ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer) throws Exception {
+    test(testBuilderConsumer, ThrowableConsumer.empty());
+  }
+
+  private void test(
+      ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer,
+      ThrowableConsumer<R8TestCompileResult> compileResultBuilder)
+      throws Exception {
     testForR8(parameters.getBackend())
         .addLibraryFiles(
             ToolHelper.getMostRecentAndroidJar(),
@@ -48,8 +56,9 @@
         .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
         .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
         .allowUnusedProguardConfigurationRules(kotlinc.isNot(KOTLINC_1_3_72))
-        .apply(consumer)
+        .apply(testBuilderConsumer)
         .compile()
+        .apply(compileResultBuilder)
         .apply(assertUnusedKeepRuleForKotlinMetadata(kotlinc.isNot(KOTLINC_1_3_72)));
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
index e505e23..b0a87ee 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
@@ -48,19 +48,13 @@
   @Override
   public void configure(R8TestBuilder<?> builder) {
     builder.enableInliningAnnotations();
+    builder.enableExperimentalMapFileVersion();
   }
 
   @Test
   public void testSourceFileAndLineNumberTable() throws Exception {
     runTest(
         ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
-        // For the desugaring to companion classes the retrace stacktrace is still the same
-        // as the mapping file has a fully qualified class name in the method mapping, e.g.:
-        //
-        // com.android.tools.r8.naming.retrace.InterfaceWithStaticMethod$-CC
-        //   -> com.android.tools.r8.naming.retrace.a:1:1:void
-        // com.android.tools.r8.naming.retrace.InterfaceWithStaticMethod$.staticMethod():80:80
-        //   -> a
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) ->
             assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace)));
   }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
index 7904ede..38ef3038 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsInlineIntoStaticRetraceTest.java
@@ -38,6 +38,7 @@
   @Override
   public void configure(R8TestBuilder<?> builder) {
     builder.enableInliningAnnotations();
+    builder.enableExperimentalMapFileVersion();
   }
 
   @Override
@@ -55,13 +56,9 @@
   public void testSourceFileAndLineNumberTable() throws Exception {
     runTest(
         ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
-        // For the desugaring to companion classes the retrace stacktrace is still the same
-        // as the mapping file has a fully qualified class name in the method mapping, e.g.:
-        //
-        // com.android.tools.r8.naming.retrace.InterfaceWithDefaultMethod1$-CC
-        //   -> com.android.tools.r8.naming.retrace.a:1:1:void
-        // com.android.tools.r8.naming.retrace.InterfaceWithDefaultMethod1.defaultMethod1():80:80
-        //   -> a
+        // Companion methods are treated as having inlined the interface method code.
+        // If compiling with synthetic marking support in the mapping file, the synthetic frames
+        // are removed and the trace will be equal to RI.
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) ->
             assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace)));
   }
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
index 04f722d..599fa3b 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -55,6 +55,10 @@
     // TODO(b/186015503): This test fails when mapping via PCs.
     //  also the test should be updated to use TestParameters and api levels.
     assumeTrue("b/186015503", !backend.isDex() || mode != CompilationMode.RELEASE);
+    // This also fails when desugaring due to the change in companion method stacks.
+    assumeTrue(
+        ToolHelper.getMinApiLevelForDexVm()
+            .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport()));
     runTest(
         ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
         // For the desugaring to companion classes the retrace stacktrace is still the same
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentTest.java
new file mode 100644
index 0000000..4e44d1f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentTest.java
@@ -0,0 +1,94 @@
+// 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.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticMethodWithConstantArgumentTest 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)
+        .addOptionsModification(
+            options -> {
+              CallSiteOptimizationOptions callSiteOptimizationOptions =
+                  options.callSiteOptimizationOptions();
+              callSiteOptimizationOptions.setEnableExperimentalArgumentPropagation();
+              callSiteOptimizationOptions.setEnableConstantPropagation();
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              // The test() method has been optimized.
+              MethodSubject testMethodSubject = mainClassSubject.uniqueMethodWithName("test");
+              assertThat(testMethodSubject, isPresent());
+              // TODO(b/190154391): The parameter x should be removed.
+              assertEquals(1, testMethodSubject.getProgramMethod().getParameters().size());
+              assertTrue(
+                  testMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf));
+
+              assertThat(mainClassSubject.uniqueMethodWithName("dead"), isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello", "Hello", "Hello");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      test(42);
+      test(42);
+      test(42);
+    }
+
+    @NeverInline
+    static void test(int x) {
+      if (x == 42) {
+        System.out.println("Hello");
+      } else {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Unreachable");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentThroughCallChainTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentThroughCallChainTest.java
new file mode 100644
index 0000000..b0387b2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/StaticMethodWithConstantArgumentThroughCallChainTest.java
@@ -0,0 +1,125 @@
+// 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.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticMethodWithConstantArgumentThroughCallChainTest 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)
+        .addOptionsModification(
+            options -> {
+              CallSiteOptimizationOptions callSiteOptimizationOptions =
+                  options.callSiteOptimizationOptions();
+              callSiteOptimizationOptions.setEnableExperimentalArgumentPropagation();
+              callSiteOptimizationOptions.setEnableConstantPropagation();
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              // The test1(), test2(), and test3() methods have been optimized.
+              for (int i = 1; i <= 3; i++) {
+                MethodSubject testMethodSubject = mainClassSubject.uniqueMethodWithName("test" + i);
+                assertThat(testMethodSubject, isPresent());
+                // TODO(b/190154391): The parameter x should be removed.
+                assertEquals(1, testMethodSubject.getProgramMethod().getParameters().size());
+                assertTrue(
+                    testMethodSubject.streamInstructions().noneMatch(InstructionSubject::isIf));
+              }
+
+              assertThat(mainClassSubject.uniqueMethodWithName("dead"), isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "Hello from test1()",
+            "Hello from test2()",
+            "Hello from test3()",
+            "Hello from test1()",
+            "Hello from test2()",
+            "Hello from test3()",
+            "Hello from test1()",
+            "Hello from test2()",
+            "Hello from test3()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      test1(42);
+      test1(42);
+      test1(42);
+    }
+
+    @NeverInline
+    static void test1(int x) {
+      if (x == 42) {
+        System.out.println("Hello from test1()");
+      } else {
+        dead();
+      }
+      test2(x);
+    }
+
+    @NeverInline
+    static void test2(int x) {
+      if (x == 42) {
+        System.out.println("Hello from test2()");
+      } else {
+        dead();
+      }
+      test3(x);
+    }
+
+    @NeverInline
+    static void test3(int x) {
+      if (x == 42) {
+        System.out.println("Hello from test3()");
+      } else {
+        dead();
+      }
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Unreachable");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithSuffixRenamingConfigurationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithSuffixRenamingConfigurationTest.java
deleted file mode 100644
index dcfd6c3..0000000
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithSuffixRenamingConfigurationTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.repackage;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.repackaging.Repackaging.SuffixRenamingRepackagingConfiguration;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class RepackageWithSuffixRenamingConfigurationTest extends RepackageTestBase {
-
-  public RepackageWithSuffixRenamingConfigurationTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    super(flattenPackageHierarchyOrRepackageClasses, parameters);
-  }
-
-  @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepMainRule(TestClass.class)
-        .addKeepClassRules(GreeterFoo.class)
-        .addOptionsModification(
-            options -> {
-              options.testing.repackageWithNoMinification = true;
-              options.testing.repackagingConfigurationFactory =
-                  appView ->
-                      new SuffixRenamingRepackagingConfiguration("Foo", appView.dexItemFactory());
-            })
-        .apply(this::configureRepackaging)
-        .enableInliningAnnotations()
-        .noMinification()
-        .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
-  }
-
-  private void inspect(CodeInspector inspector) {
-    ClassSubject greeterSubject = inspector.clazz(Greeter.class);
-    assertEquals(GreeterFoo.class.getTypeName() + "$1", greeterSubject.getFinalName());
-
-    ClassSubject greeterFooSubject = inspector.clazz(GreeterFoo.class);
-    assertEquals(GreeterFoo.class.getTypeName(), greeterFooSubject.getFinalName());
-  }
-
-  public static class TestClass {
-
-    public static void main(String[] args) {
-      Greeter.greet();
-      GreeterFoo.greet();
-    }
-  }
-
-  public static class Greeter extends Exception {
-
-    @NeverInline
-    public static void greet() {
-      System.out.print("Hello");
-    }
-  }
-
-  public static class GreeterFoo extends Exception {
-
-    @NeverInline
-    public static void greet() {
-      System.out.println(" world!");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
index f1ea8f0..09e9882 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Base.class, "collect", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     assertTrue(resolutionResult.isSingleResolution());
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 2b560e2..b176930 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -14,8 +14,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.singletarget.Main;
 import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
 import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -204,7 +204,7 @@
   public void lookupVirtualTargets() {
     DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
     Assert.assertNotNull(appInfo.resolveMethodOnClass(method).getSingleTarget());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     if (resolutionResult.isVirtualTarget()) {
       LookupResult lookupResult =
           resolutionResult.lookupVirtualDispatchTargets(
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index 4d4419f..473f349 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -91,7 +91,8 @@
 
   @Test
   public void resolveTarget() {
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
     DexClass context = appInfo.definitionFor(methodOnB.holder);
     assertTrue(resolutionResult.isIllegalAccessErrorResult(context, appInfo));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 6be194c..cc2005d 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -107,7 +107,8 @@
 
   @Test
   public void testResolution() {
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
     assertTrue(resolutionResult.isFailedResolution());
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index edc4c42..cbad874 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -16,8 +16,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
@@ -129,7 +129,7 @@
   public void lookupSingleTarget() {
     DexProgramClass bClass = appInfo.definitionForProgramType(methodOnBReference.holder);
     ProgramMethod methodOnB = bClass.lookupProgramMethod(methodOnBReference);
-    ResolutionResult resolutionResult =
+    MethodResolutionResult resolutionResult =
         appInfo.resolveMethodOnInterface(methodOnBReference.holder, methodOnBReference);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnBReference, resolved.getReference());
@@ -141,7 +141,7 @@
 
   @Test
   public void lookupVirtualTargets() {
-    ResolutionResult resolutionResult =
+    MethodResolutionResult resolutionResult =
         appInfo.resolveMethodOnInterface(methodOnBReference.holder, methodOnBReference);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnBReference, resolved.getReference());
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index 7843995..c6e9206 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -174,7 +174,8 @@
   @Test
   public void lookupSingleTarget() {
     DexProgramClass bClass = appInfo.definitionForProgramType(methodOnB.holder);
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
@@ -185,7 +186,8 @@
 
   @Test
   public void lookupVirtualTargets() {
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index 821bc4c..3803d1a 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -17,8 +17,8 @@
 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.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -121,7 +121,8 @@
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
-    ResolutionResult resolutionResult = appInfo.resolveMethodOn(declaredClassDefinition, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOn(declaredClassDefinition, method);
 
     if (!symbolicReferenceIsDefiningType) {
       // The targeted method is a private interface method and thus not a maximally specific method.
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
index 83d363b..4787b97 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
@@ -16,8 +16,8 @@
 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.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
@@ -119,7 +119,8 @@
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
-    ResolutionResult resolutionResult = appInfo.resolveMethodOn(declaredClassDefinition, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOn(declaredClassDefinition, method);
 
     // The targeted method is a private interface method and thus not a maximally specific method.
     assertTrue(resolutionResult instanceof NoSuchMethodResult);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
index bec0f69..b14ab55 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -95,7 +95,8 @@
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
-    ResolutionResult resolutionResult = appInfo.resolveMethodOn(declaredClassDefinition, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOn(declaredClassDefinition, method);
 
     // Verify that the resolved method is on the defining class.
     assertEquals(
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 56258b4..9c596c9 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -119,7 +119,8 @@
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
-    ResolutionResult resolutionResult = appInfo.resolveMethodOn(declaredClassDefinition, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOn(declaredClassDefinition, method);
 
     // Resolution fails when there is a mismatch between the symbolic reference and the definition.
     if (!symbolicReferenceIsDefiningType) {
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
index b4dab4e..d65dabf 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
@@ -97,7 +97,8 @@
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
-    ResolutionResult resolutionResult = appInfo.resolveMethodOn(declaredClassDefinition, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOn(declaredClassDefinition, method);
 
     // Verify that the resolved method is on the defining class.
     assertEquals(
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 9785c92..760215a 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -13,7 +13,7 @@
 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.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -73,7 +73,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index 1df6b63..9a549f2 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -13,7 +13,7 @@
 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.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -72,7 +72,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 7ab3821..5251469 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -14,7 +14,7 @@
 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.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -76,7 +76,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 2c0dbca..ec1222f 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -12,7 +12,7 @@
 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.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -71,7 +71,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
index cd2679e..ea12cad 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
@@ -11,7 +11,7 @@
 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.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
@@ -59,7 +59,7 @@
     DexProgramClass aClass =
         appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
     assertEquals(OptionalBool.TRUE, resolutionResult.isAccessibleFrom(aClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
index 008a478..8341bba 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
@@ -12,7 +12,7 @@
 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.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.access.indirectmethod.pkg.C;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.OptionalBool;
@@ -65,7 +65,7 @@
     DexProgramClass cClass =
         appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
     assertEquals(
         OptionalBool.TRUE, resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index 6c6d9fe..4911f70 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
@@ -45,7 +45,7 @@
         buildClasses(CLASSES).addLibraryFile(parameters.getDefaultRuntimeLibrary()).build();
     AppInfoWithLiveness appInfo = computeAppViewWithLiveness(app, Main.class).appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     // Currently R8 will resolve to L::f as that is the first in the topological search.
     // Resolution may return any of the matches, so it is valid if this expectation changes.
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index 273fb50..d6edf4f 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -50,7 +50,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index 9897acd..77fad9a 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -50,7 +50,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index baa7747..49edcae 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -53,7 +53,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 904640e..1aa65c6 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -53,7 +53,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 6406ea0..41307e8 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -52,7 +52,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     Set<String> holders = new HashSet<>();
     resolutionResult
         .asFailedResolution()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index de6da74..9b9a45d 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -46,7 +46,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 7325cc1..50dc8d8 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -46,7 +46,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index 296f95f..0139049 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -53,7 +53,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     Set<String> holders = new HashSet<>();
     resolutionResult
         .asFailedResolution()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index fbece8f..f8a46ca 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index 8d0eb98..4449400 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index e7532b1..6701adc 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
@@ -107,7 +107,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
index 63d9316..ae340f7 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 5f35faa..9af861c 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
index 112c684..5912e69 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
index d011624..3503bdf 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
index 9caa8d2..512e775 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
index aef0b3d..682d447 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
index d6230c7..bbf73d7 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,8 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethodOnInterface(method.holder, method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
index 129c033..fe3a74c 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.Abstract;
 import com.android.tools.r8.resolution.packageprivate.a.I;
 import com.android.tools.r8.resolution.packageprivate.a.NonAbstract;
@@ -72,7 +72,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Abstract.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Abstract.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
index 9d06f40..4b2877d 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.A;
 import com.android.tools.r8.resolution.packageprivate.a.A.B;
 import com.android.tools.r8.resolution.packageprivate.a.D;
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
index a9eddd1..be967fb 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.PackagePrivateReentryTest.C;
 import com.android.tools.r8.resolution.packageprivate.a.A;
 import com.android.tools.r8.resolution.packageprivate.a.A.B;
@@ -63,7 +63,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
index fd40588..8262061 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.Abstract;
 import com.android.tools.r8.resolution.packageprivate.a.AbstractWidening;
 import com.android.tools.r8.resolution.packageprivate.a.I;
@@ -73,7 +73,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
index 58f4f6d..876a991 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.Abstract;
 import com.android.tools.r8.resolution.packageprivate.a.I;
 import com.android.tools.r8.resolution.packageprivate.a.J;
@@ -67,7 +67,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     assertTrue(resolutionResult.isAccessibleFrom(context, appView).isFalse());
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
index d5b9556..219c239 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.packageprivate.a.A;
 import com.android.tools.r8.resolution.packageprivate.a.A.B;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -60,7 +60,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
index e116329..a8bbdf4 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -118,7 +118,7 @@
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
     DexMethod fooC = buildNullaryVoidMethod(C.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolution = appInfo.resolveMethodOnClass(fooA);
+    MethodResolutionResult resolution = appInfo.resolveMethodOnClass(fooA);
     DexProgramClass context = appView.definitionForProgramType(typeMain);
     DexProgramClass upperBound = appView.definitionForProgramType(typeA);
     DexProgramClass lowerBound = appView.definitionForProgramType(typeC);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
index ee3387e..4da75f6 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index 73b0af5..f6368cb 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 1f27130..4673e7c 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
index 11e259c..d6d5dc8 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
index f19413c..10c5b6f 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -19,7 +19,7 @@
 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.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
@@ -59,7 +59,7 @@
                       Main.class);
               AppInfoWithLiveness appInfo = appView.appInfo();
               DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-              ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+              MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
               assertTrue(resolutionResult.isSingleResolution());
               DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
               DexProgramClass main = appView.definitionForProgramType(mainType);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index 511d697..e6e6f6c 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
index d630f6f..3b675c7 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.google.common.collect.ImmutableSet;
@@ -87,7 +87,7 @@
             });
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(initial, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         DexProgramClass.asProgramClassOrNull(
             appView
@@ -238,7 +238,7 @@
                 .build());
     AppInfoWithClassHierarchy appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexType typeA = buildType(A.class, appInfo.dexItemFactory());
     DexType typeB = buildType(B.class, appInfo.dexItemFactory());
     DexProgramClass classB = appInfo.definitionForProgramType(typeB);
@@ -273,7 +273,7 @@
             Unrelated.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Unrelated.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
index 3fc0355..001075d 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.virtualtargets.package_a.Middle;
 import com.android.tools.r8.resolution.virtualtargets.package_a.Top;
 import com.android.tools.r8.resolution.virtualtargets.package_a.TopRunner;
@@ -62,7 +62,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Top.class, "clear", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(TopRunner.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
index f081ef1..84021e7 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
 import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunner;
 import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModelRunnerWithCast;
@@ -68,7 +68,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(
             buildType(ViewModelRunner.class, appInfo.dexItemFactory()));
@@ -119,7 +119,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
@@ -171,7 +171,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(
             buildType(ViewModelRunnerWithCast.class, appInfo.dexItemFactory()));
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
index 86e9ecb..eda5153 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.resolution.virtualtargets.package_a.A;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Box;
@@ -61,7 +61,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(B.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java
index 092f9b5..3a4a369 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -54,7 +54,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(builder.build(), Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java
index 8915fa5..10fb0d9 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,7 +50,7 @@
             PackagePrivateChainTest.Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 092bcb8..536f420 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -60,7 +60,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    ResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 24fad30..e0eb6ca 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -74,7 +74,6 @@
   }
 
   private int getObfuscatedLinePosition() {
-    // TODO(b/185358363): This should go away when we correctly retrace.
     return kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) ? 43 : 32;
   }
 
@@ -122,10 +121,7 @@
                           8,
                           FILENAME_INLINE),
                       LinePosition.create(
-                          mainSubject.asFoundMethodSubject(),
-                          1,
-                          getObfuscatedLinePosition(),
-                          FILENAME_INLINE));
+                          mainSubject.asFoundMethodSubject(), 1, 21, FILENAME_INLINE));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index ed842eb..032a370 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -122,7 +122,7 @@
                   LinePosition.stack(
                       LinePosition.create(
                           inlineExceptionStatic(kotlinInspector), 2, 8, FILENAME_INLINE_STATIC),
-                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15, mainFileName));
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 9, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -155,7 +155,7 @@
                           2,
                           15,
                           FILENAME_INLINE_INSTANCE),
-                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13, mainFileName));
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 7, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -187,7 +187,7 @@
                           inlineExceptionStatic(kotlinInspector), 3, 8, FILENAME_INLINE_STATIC),
                       // TODO(b/146399675): There should be a nested frame on
                       //  retrace.NestedInlineFunctionKt.nestedInline(line 10).
-                      LinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19, mainFileName));
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 3, 10, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -218,8 +218,8 @@
                       LinePosition.create(
                           inlineExceptionStatic(kotlinInspector), 2, 8, FILENAME_INLINE_STATIC),
                       // TODO(b/146399675): There should be a nested frame on
-                      //  retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine(line 15).
-                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20, mainFileName));
+                      //  retrace.NestedInlineFunctionKt.nestedInline(line 10).
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 10, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
index 11829f6..f860ca2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
@@ -100,7 +100,7 @@
     List<String> cmdline = new ArrayList<>();
     cmdline.add(TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString());
     cmdline.add("-jar");
-    cmdline.add(ToolHelper.JACOCO_CLI);
+    cmdline.add(ToolHelper.JACOCO_CLI.toString());
     cmdline.add("instrument");
     cmdline.add(input.toString());
     cmdline.add("--dest");
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/NoLongerSatisfiedIfRuleTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/NoLongerSatisfiedIfRuleTest.java
index 02dde28..fdef324 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/NoLongerSatisfiedIfRuleTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/NoLongerSatisfiedIfRuleTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking.ifrule;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -51,8 +52,7 @@
 
     ClassSubject bClassSubject = inspector.clazz(B.class);
     assertThat(bClassSubject, isPresent());
-    // TODO(b/153910208): Should be absent since A is dead.
-    assertThat(bClassSubject.uniqueMethodWithName("m"), isPresent());
+    assertThat(bClassSubject.uniqueMethodWithName("m"), isAbsent());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/KeepIfPresentRuleWithVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/KeepIfPresentRuleWithVerticalClassMergingTest.java
index 541253a..8fab4c1 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/KeepIfPresentRuleWithVerticalClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/KeepIfPresentRuleWithVerticalClassMergingTest.java
@@ -26,7 +26,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public KeepIfPresentRuleWithVerticalClassMergingTest(TestParameters parameters) {
@@ -41,7 +41,7 @@
         .addKeepRules(
             "-if class * extends " + A.class.getTypeName(), "-keep class <1> { <init>(...); }")
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
@@ -53,8 +53,7 @@
               assertThat(classBSubject.init(), isPresent());
               assertThat(classBSubject.uniqueMethodWithName("greet"), isPresent());
               assertEquals(2, classBSubject.allMethods().size());
-            }
-        )
+            })
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello world!");
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index accb38b..e2b6d2e 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -27,9 +28,9 @@
     this.testParameters = parameters;
   }
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private class Result {
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java
index a146368..efdfb71 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/UnsatisfiedDependentNoObfuscationTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking.keepclassmembers;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
@@ -34,8 +34,8 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepRules(
-            // TODO(b/192636793): This rule should not have any impact on the compilation, since
-            //  GreeterConsumer is dead.
+            // This rule should not have any impact on the compilation, since GreeterConsumer is
+            // dead.
             "-keepclassmembers,includedescriptorclasses class "
                 + GreeterConsumer.class.getTypeName()
                 + " {",
@@ -44,11 +44,7 @@
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(
-            inspector -> {
-              // TODO(b/192636793): This should be renamed.
-              assertThat(inspector.clazz(Greeter.class), isPresentAndNotRenamed());
-            })
+        .inspect(inspector -> assertThat(inspector.clazz(Greeter.class), isPresentAndRenamed()))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 402d6b3..4ea9a82 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -120,6 +120,11 @@
   }
 
   @Override
+  public boolean isInterface() {
+    throw new Unreachable("Cannot determine if an absent class is an interface");
+  }
+
+  @Override
   public String getOriginalName() {
     return reference.getTypeName();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 8beec8a..f552a25 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -171,6 +171,8 @@
   @Override
   public abstract ClassAccessFlags getAccessFlags();
 
+  public abstract boolean isInterface();
+
   public abstract boolean isAbstract();
 
   public abstract boolean isAnnotation();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 1114bce..318f0d9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -294,6 +294,11 @@
   }
 
   @Override
+  public boolean isInterface() {
+    return dexClass.isInterface();
+  }
+
+  @Override
   public boolean isImplementing(ClassSubject subject) {
     assertTrue(subject.isPresent());
     for (DexType itf : getDexProgramClass().interfaces) {
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 45ffbf2..11fcaa4 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -584,6 +584,7 @@
                   && EdgeKindPredicate.isAnnotatedOn.test(infos));
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public boolean isKeptByReferenceInAnnotationOn(
         QueryNode annotationNode, QueryNode annotatedNode) {
@@ -913,7 +914,7 @@
   }
 
   private QueryNode getQueryNode(GraphNode node, String absentString) {
-    return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
+    return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl<>(this, node);
   }
 
   private boolean isPureCompatTarget(GraphNode target) {
diff --git a/third_party/android_jar/api-database.tar.gz.sha1 b/third_party/android_jar/api-database.tar.gz.sha1
index 97d5f12..2bbe555 100644
--- a/third_party/android_jar/api-database.tar.gz.sha1
+++ b/third_party/android_jar/api-database.tar.gz.sha1
@@ -1 +1 @@
-a3e0351d71082eb74073576e11c0632191fd8530
\ No newline at end of file
+829d7f32a482c16a2008a8878c33c58637c21a56
\ No newline at end of file
diff --git a/third_party/jacoco/0.8.6.tar.gz.sha1 b/third_party/jacoco/0.8.6.tar.gz.sha1
index 47fefc1..8ff8131 100644
--- a/third_party/jacoco/0.8.6.tar.gz.sha1
+++ b/third_party/jacoco/0.8.6.tar.gz.sha1
@@ -1 +1 @@
-471ccc4aa6d69a684aa400f08dc6a15e17b4ba25
\ No newline at end of file
+b3927f234695ffd3004d248741a125782a6b93b7
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-16/linux.tar.gz.sha1 b/third_party/openjdk/jdk-16/linux.tar.gz.sha1
new file mode 100644
index 0000000..0a22059
--- /dev/null
+++ b/third_party/openjdk/jdk-16/linux.tar.gz.sha1
@@ -0,0 +1 @@
+8fa0d8caeed71708c442eabb610b794c1efa63a3
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-16/osx.tar.gz.sha1 b/third_party/openjdk/jdk-16/osx.tar.gz.sha1
new file mode 100644
index 0000000..ebbc803
--- /dev/null
+++ b/third_party/openjdk/jdk-16/osx.tar.gz.sha1
@@ -0,0 +1 @@
+b7db69925ff470d6b118e70f28f39451afb027b7
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-16/windows.tar.gz.sha1 b/third_party/openjdk/jdk-16/windows.tar.gz.sha1
new file mode 100644
index 0000000..fe6a7bd
--- /dev/null
+++ b/third_party/openjdk/jdk-16/windows.tar.gz.sha1
@@ -0,0 +1 @@
+ce1494575976dbc8ac4702ef6f08c15cc6b38c47
\ No newline at end of file
diff --git a/tools/retrace.py b/tools/retrace.py
index c087b31..d28c222 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -46,10 +46,15 @@
       action='store_true',
       help='Disables diagnostics printing to stdout.')
   parser.add_argument(
-    '--debug-agent',
-    default=None,
-    action='store_true',
-    help='Attach a debug-agent to the retracer java process.')
+      '--debug-agent',
+      default=None,
+      action='store_true',
+      help='Attach a debug-agent to the retracer java process.')
+  parser.add_argument(
+      '--regex',
+      default=None,
+      help='Sets a custom regular expression used for parsing'
+  )
   return parser.parse_args()
 
 
@@ -62,9 +67,10 @@
       args.stacktrace,
       args.no_r8lib,
       quiet=args.quiet,
-      debug=args.debug_agent)
+      debug=args.debug_agent,
+      regex=args.regex)
 
-def run(map_path, stacktrace, no_r8lib, quiet=False, debug=False):
+def run(map_path, stacktrace, no_r8lib, quiet=False, debug=False, regex=None):
   retrace_args = [jdk.GetJavaExecutable()]
 
   if debug:
@@ -78,6 +84,10 @@
     map_path
   ]
 
+  if regex:
+    retrace_args.append('--regex')
+    retrace_args.append(regex)
+
   if quiet:
     retrace_args.append('--quiet')
 
diff --git a/tools/test.py b/tools/test.py
index 157bd28..9c35352 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -171,6 +171,10 @@
       help='Print the execution time of the slowest tests..',
       default=False, action='store_true')
   result.add_option(
+      '--testing-state-name',
+      help='Set an explict name for the testing state '
+          '(used in conjunction with --with/reset-testing-state).')
+  result.add_option(
       '--with-testing-state',
       help='Run/resume tests using testing state.',
       default=False, action='store_true')
@@ -322,6 +326,8 @@
     gradle_args.append('-Preset-testing-state')
   elif options.with_testing_state:
     gradle_args.append('-Ptesting-state')
+  if options.testing_state_name:
+    gradle_args.append('-Ptesting-state-name=' + options.testing_state_name)
 
   # Build an R8 with dependencies for bootstrapping tests before adding test sources.
   gradle_args.append('r8WithDeps')